corenet: bringing LTE support and improving 3G support

This commit is contained in:
mitshell 2018-01-02 14:57:39 +01:00
parent 5b84e8280f
commit 9b0b853e49
22 changed files with 9266 additions and 1983 deletions

View File

@ -28,11 +28,12 @@
#*/
from .utils import *
from .ProcCNS1ap import *
class ENBd(object):
"""eNB handler within a CorenetServer instance
responsible for S1AP signaling
responsible for S1AP signalling
"""
#--------------------------------------------------------------------------#
@ -42,19 +43,20 @@ class ENBd(object):
# verbosity level
DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
# to log S1AP PDU
TRACE_ASN_S1AP = False
TRACE_ASN_S1AP = False
# to keep track of all S1AP procedures
TRACK_PROC_S1AP = True
TRACK_PROC = True
# Radio Access Technology remainder
RAT = RAT_UTRA
RAT = RAT_EUTRA
# ID: (PLMN, CellID)
ID = (None, None)
# SCTP socket
SK = None
Addr = None
SK = None
SKSid = 0
Addr = None
# Server reference
Server = None
@ -74,17 +76,17 @@ class ENBd(object):
elif logtype in self.DEBUG:
log('[%s] [ENB: %s.%s] %s' % (logtype, self.ID[0], self.ID[1], msg))
def __init__(self, server, sk):
self.connect(server, sk)
def __init__(self, server, sk, sid):
self.connect(server, sk, sid)
#
# init ENB config dict
self.Config = {}
#
# dict of ongoing S1AP procedures (indexed by their procedure code)
self.ProcS1ap = {}
self.Proc = {}
# procedure code of the last procedure emitting a pdu toward the RAN
self.ProcS1apLast = None
# list of tracked procedures (requires TRACK_PROC_S1AP = True)
self.ProcLast = None
# list of tracked procedures (requires TRACK_PROC = True)
self._proc = []
#
# counter for UE context id
@ -94,24 +96,271 @@ class ENBd(object):
# network socket operations
#--------------------------------------------------------------------------#
def connect(self, server, sk):
def connect(self, server, sk, sid):
self.Server = server
self.SK = sk
self.Addr = sk.getpeername()
self.SK = sk
self.SKSid = sid
self.Addr = sk.getpeername()
def disconnect(self):
del self.Server, self.SK, self.Addr
del self.Server, self.SK, self.Addr, self.SKSid
def is_connected(self):
return self.SK is not None
#--------------------------------------------------------------------------#
# handling of RAN link procedures
# handling of non-UE-associated S1AP signalling procedures
#--------------------------------------------------------------------------#
def process_s1ap_pdu(self, pdu):
"""process a S1AP PDU sent by the ENB
and return a list of S1APAP PDU(s) to be sent back to it
def process_s1ap_pdu(self, pdu_rx):
"""process an S1AP PDU sent by the eNB for non-UE-associated signalling
and return a list of S1AP PDU(s) to be sent back to it
"""
return []
errcause = None
if pdu_rx[0] == 'initiatingMessage':
# eNB-initiated procedure, instantiate it
try:
Proc = S1APNonUEProcEnbDispatcher[pdu_rx[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid S1AP PDU, initiatingMessage, code %i'\
% pdu_rx[1]['procedureCode'])
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = self.init_s1ap_proc(S1APErrorIndNonUECN, Cause=errcause)
else:
if self.TRACK_PROC:
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu_rx )
if Proc.Class == 2 and Proc.errcause:
Err = self.init_s1ap_proc(S1APErrorIndNonUECN, Cause=Proc.errcause)
self.ProcLast = Err.Code
return Err.send()
elif Proc.Class == 1 or errcause:
self.ProcLast = Proc.Code
return Proc.send()
else:
pdu_tx = []
for ProcRet in Proc.trigger():
pdu_tx.extend( ProcRet.send() )
self.ProcLast = ProcRet.Code
return pdu_tx
#
else:
# CN-initiated procedure, transfer the PDU to it
try:
Proc = self.Proc[pdu_rx[1]['procedureCode']]
except:
self._log('ERR', 'invalid S1AP PDU, %s, code %i'\
% (pdu_rx[0], pdu_rx[1]['procedureCode']))
errcause = ('protocol', 'message-not-compatible-with-receiver-state')
Proc = self.init_s1ap_proc(S1APErrorIndNonUECN, Cause=errcause)
# process the PDU within the procedure
Proc.recv( pdu_rx )
if Proc.errcause:
Err = self.init_s1ap_proc(S1APErrorIndNonUECN, Cause=Proc.errcause)
self.ProcLast = Err.Code
return Err.send()
elif errcause:
self.ProcLast = Proc.Code
return Proc.send()
else:
pdu_tx = []
for ProcRet in Proc.trigger():
pdu_tx.extend( ProcRet.send() )
self.ProcLast = ProcRet.Code
return pdu_tx
def init_s1ap_proc(self, ProcClass, **kw):
"""initialize a CN-initiated S1AP procedure of class `ProcClass' for
non-UE-associated signalling, encode the initiatingMessage PDU with given
**kw and return the procedure
"""
if not issubclass(ProcClass, S1APNonUESigProc):
self._log('WNG', 'initializing an invalid S1AP procedure, %s' % ProcClass.__name__)
if ProcClass.Code in self.Proc:
self._log('ERR', 'an S1AP procedure %s is already ongoing, unable to start a new one'\
% ProcClass.__name__)
return None
Proc = ProcClass(self)
if Proc.Code in S1APNonUEProcCnDispatcher and Proc.Class == 1:
# store the procedure, which requires a response from the eNB
self.Proc[Proc.Code] = Proc
if self.TRACK_PROC:
# keep track of the procedure
self._proc.append( Proc )
Proc.encode_pdu('ini', **kw)
return Proc
def start_s1ap_proc(self, ProcClass, **kw):
"""initialize a CN-initiated S1AP procedure of class `ProcClass' for
non-UE-associated signalling, encode the initiatingMessage PDU with given
**kw and send the PDU generated by the procedure to the eNB
"""
if not self.is_connected():
self._log('ERR', 'not connected')
return 0
Proc = self.init_s1ap_proc(ProcClass, **kw)
if Proc is None:
return 0
self.ProcLast, cnt = Proc.Code, 0
for pdu_tx in Proc.send():
if self.Server.send_s1ap_pdu(self, pdu_tx, self.SKSid):
cnt += 1
return cnt
#--------------------------------------------------------------------------#
# CN-initiated non-UE-associated S1AP signalling procedures
#--------------------------------------------------------------------------#
def send_error_ind(self, cause, **IEs):
"""start a S1APErrorIndNonUECN with the given S1AP cause
IEs can contain any of the optional or extended IEs
"""
# send an S1APErrorInd to the eNB
IEs['Cause'] = cause
# send to the RNC in connection-less signaling
ret = self.start_s1ap_proc(S1APErrorIndNonUECN, **IEs)
if not ret:
self._log('ERR', 'send_error_ind: error')
return True if ret else False
def reset(self, cause=('misc', 115), **IEs):
"""start an S1APResetCN toward the eNB after having deleted some or all
UE-related S1 resources
IEs can contain any of the optional or extended IEs
"""
# TODO
pass
def page(self, **IEs):
"""start an S1APPaging toward the eNB
IEs should be set by the UE handler stack
"""
ret = self.start_s1ap_proc(RANAPPaging, **IEs)
if not ret:
self._log('ERR', 'page: error')
return True if ret else False
#--------------------------------------------------------------------------#
# handling of UE-associated S1AP signalling procedures
#--------------------------------------------------------------------------#
def process_s1ap_ue_pdu(self, pdu_rx, sid):
"""process an S1AP PDU sent by the eNB for UE-associated signalling with
a given SCTP stream id
and return a list of S1AP PDU(s) to be sent back to it
"""
if pdu_rx[0] == 'initiatingMessage' and pdu_rx[1]['procedureCode'] == 12:
# initialUEMessage, retrieve / create the UE instance
ue, ctx_id = self.get_ued(pdu_rx)
if ue is None:
self._log('ERR', 'unknown UE trying to connect')
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = self.init_s1ap_proc(S1APErrorIndNonUECN, Cause=errcause)
Proc.recv( pdu_rx )
self.ProcLast = Proc.Code
return Proc.send()
else:
self.set_ue_s1(ue, ctx_id)
try:
ue.set_ran(self, ctx_id, sid)
except Exception as err:
self._log('ERR', 'UE connected to several RAN, %r' % err)
return []
else:
ctx_id = self.get_enb_ue_ctx_id(pdu_rx)
if ctx_id is None:
self._log('ERR', 'no eNB UE context id provided')
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = self.init_s1ap_proc(S1APErrorIndNonUECN, Cause=errcause)
Proc.recv( pdu_rx )
self.ProcLast = Proc.Code
return Proc.send()
else:
try:
ue = self.UE[ctx_id]
except:
self._log('ERR', 'invalid eNB UE context id provided')
errcause = ('radioNetwork', 'unknown-enb-ue-s1ap-id')
Proc = self.init_s1ap_proc(S1APErrorIndNonUECN, Cause=errcause)
Proc.recv( pdu_rx )
self.ProcLast = Proc.Code
return Proc.send()
else:
return ue.S1.process_s1ap_pdu(pdu_rx)
def set_ue_s1(self, ued, ctx_id):
self.UE[ctx_id] = ued
def unset_ue_s1(self, ctx_id):
try:
del self.UE[ctx_id]
except:
self._log('WNG', 'no UE with S1 context-id %i to unset' % ctx_id)
def get_ued(self, pdu_rx):
enb_ue_id, nas_pdu, s_tmsi, tai = None, None, None, None
for ie in pdu_rx[1]['value'][1]['protocolIEs']:
if ie['id'] == 8:
# ENB-UE-S1AP-ID
enb_ue_id = ie['value'][1]
elif ie['id'] == 26:
# NAS-PDU
nas_pdu = ie['value'][1]
elif ie['id'] == 67:
# TAI
tai = ie['value'][1]
elif ie['id'] == 96:
# S-TMSI
s_tmsi = ie['value'][1]
if enb_ue_id is None or not nas_pdu or not tai:
# missing mandatory IE
return None, 0
plmn = plmn_buf_to_str(tai['pLMNidentity'])
if s_tmsi:
# use the S1AP S-TMSI
return self.Server.get_ued(mtmsi=bytes_to_uint(s_tmsi['m-TMSI'], 32)), enb_ue_id
else:
# use the EPSID within the NAS PDU
TS24007.IE.DECODE_INNER = False
NasRx, err = NAS.parse_NASLTE_MO(nas_pdu, inner=False)
TS24007.IE.DECODE_INNER = True
if err:
return None, enb_ue_id
sh = NasRx['SecHdr'].get_val()
if sh in (1, 3):
TS24007.IE.DECODE_INNER = False
NasRx, err = NAS.parse_NASLTE_MO(NasRx['NASMessage'].get_val(), inner=False)
TS24007.IE.DECODE_INNER = True
sh = NasRx['SecHdr'].get_val()
if sh == 0:
# clear-text NAS PDU
try:
epsid = NasRx['EPSID']['V'].get_val()
except:
return None, enb_ue_id
EpsId = NAS.EPSID()
EpsId.from_bytes(epsid)
ident = EpsId.decode()
if ident[0] == NAS.IDTYPE_IMSI:
return self.Server.get_ued(imsi=ident[1]), enb_ue_id
elif ident[0] == NAS.IDTYPE_GUTI:
# ensure PLMN, MME group, MMEC correspond
return self.Server.get_ued(mtmsi=ident[4]), enb_ue_id
else:
return None, enb_ue_id
else:
return None, enb_ue_id
def get_enb_ue_ctx_id(self, pdu_rx):
enb_ue_id = None
for ie in pdu_rx[1]['value'][1]['protocolIEs']:
if ie['id'] == 8:
# ENB-UE-S1AP-ID
enb_ue_id = ie['value'][1]
break
return enb_ue_id

View File

@ -100,10 +100,10 @@ class HNBd(SigStack):
# init HNB config dict
self.Config = {}
#
# dict of ongoing RAN procedures (indexed by their procedure code)
# they are populated only for request / response procedure
# dict of ongoing resquest-response CN-initiated RAN procedures
# indexed by their procedure code
# RUA has no request-response procedure
# RANAP procedure handled here are non-UE related
# RANAP procedure handled here are only non-UE related
self.ProcHnbap = {}
self.ProcRanap = {}
# procedure code of the last procedure emitting a pdu toward the RAN
@ -132,278 +132,82 @@ class HNBd(SigStack):
return self.SK is not None
#--------------------------------------------------------------------------#
# handling of RAN link procedures
# handling of HNBAP procedures
#--------------------------------------------------------------------------#
def process_hnbap_pdu(self, pdu):
def process_hnbap_pdu(self, pdu_rx):
"""process a HNBAP PDU sent by the HNB
and return a list of HNBAP PDU(s) to be sent back to it
"""
errcause = None
if pdu[0] == 'initiatingMessage':
if pdu_rx[0] == 'initiatingMessage':
# HNB-initiated procedure, instantiate it
try:
Proc = HNBAPProcHnbDispatcher[pdu[1]['procedureCode']](self)
Proc = HNBAPProcHnbDispatcher[pdu_rx[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid HNBAP PDU, initiatingMessage, code %i'\
% pdu[1]['procedureCode'])
% pdu_rx[1]['procedureCode'])
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = HNBAPErrorIndGW(self)
Proc.encode_pdu('ini', Cause=errcause)
Proc = self.init_hnbap_proc(HNBAPErrorIndGW, Cause=errcause)
else:
# store the procedure, if no error ind
self.ProcHnbap[Proc.Code] = Proc
if self.TRACK_PROC_HNBAP:
# keep track of the procedure
self._proc.append( Proc )
if self.TRACK_PROC_HNBAP:
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu )
if Proc.Cont['suc'] is not None or errcause is not None:
# set the last procedure code
Proc.recv( pdu_rx )
if Proc.Class == 2 and Proc.errcause:
Err = self.init_hnbap_proc(HNBAPErrorIndGW, Cause=Proc.errcause)
self.ProcHnbapLast = Err.Code
return Err.send()
elif Proc.Class == 1 or errcause:
self.ProcHnbapLast = Proc.Code
# send back any potential response to the HNB
# Proc.send() will take care to clean-up self.ProcHnbap
return Proc.send()
else:
# TODO: check in case some HNBAP would trigger() new HNBAP procedure
return []
#
else:
# GW-initiated procedure, transfer the response PDU to it
try:
Proc = self.ProcHnbap[pdu[1]['procedureCode']]
Proc = self.ProcHnbap[pdu_rx[1]['procedureCode']]
except:
self._log('ERR', 'invalid HNBAP PDU, %s, code %i'\
% (pdu[0], pdu[1]['procedureCode']))
% (pdu_rx[0], pdu_rx[1]['procedureCode']))
errcause = ('protocol', 'message-not-compatible-with-receiver-state')
Proc = HNBAPErrorIndGW(self)
Proc.encode_pdu('ini', Cause=errcause)
if self.TRACK_PROC_HNBAP:
# keep track of the procedure
self._proc.append( Proc )
Proc = self.init_hnbap_proc(HNBAPErrorIndGW, Cause=errcause)
# process the PDU within the procedure
Proc.recv( pdu )
#
if errcause is not None:
# set the last procedure code
Proc.recv( pdu_rx )
if Proc.errcause:
Err = self.init_hnbap_proc(HNBAPErrorIndGW, Cause=Proc.errcause)
self.ProcHnbapLast = Err.Code
return Err.send()
elif errcause:
self.ProcHnbapLast = Proc.Code
# send back error ind to the HNB
# Proc.send() will take care to clean-up self.ProcHnbap
return Proc.send()
else:
# TODO: check in case some HNBAP would trigger() new HNBAP procedure
return []
def process_rua_pdu(self, pdu):
"""process a RUA PDU sent by the HNB
and return a list of RUA PDU(s) to be sent back to it
"""
# WNG: RUA is a symmetric protocol with initatingMessage-only procedures
errcause = None
if pdu[0] != 'initiatingMessage':
self._log('ERR', 'invalid PDU, %s, code %i' % (pdu[0], pdu[1]['procedureCode']))
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = RUAErrorInd(self)
Proc.encode_pdu('ini', Cause=errcause)
self.ProcRuaLast = Proc.Code
else:
try:
Proc = RUAProcDispatcher[pdu[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid PDU, initiatingMessage, code %i'\
% pdu[1]['procedureCode'])
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = RUAErrorInd(self)
Proc.encode_pdu('ini', Cause=errcause)
if self.TRACK_PROC_RUA:
# keep track of the procedure
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu )
if errcause is not None:
# set the last procedure code
self.ProcRuaLast = Proc.Code
# send back error ind to the HNB
# Proc.send() will take care to clean-up self.ProcHnbap
return Proc.send()
else:
# potentially create new RUA procedures
# as outcome of the one received
snd = []
for ProcRet in Proc.trigger():
if self.TRACK_PROC_RUA:
# keep track of each procedure
self._proc.append( ProcRet )
snd.extend( ProcRet.send() )
# set the last procedure code
self.ProcRuaLast = ProcRet.Code
return snd
def process_ranap(self, buf):
"""process a RANAP PDU buffer sent by the HNB in connection-less transfer
and return a list of RANAP PDU(s) to be sent back to it
"""
# decode the RANAP PDU
if not asn_ranap_acquire():
self._log('ERR', 'unable to acquire the RANAP module')
return []
try:
PDU_RANAP.from_aper(buf)
except:
# unable to decode APER-encoded buffer
self._log('WNG', 'invalid RANAP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
# returns a RANAP error ind: protocol, transfer-syntax-error
Proc = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=('protocol', 97))
Proc.recv(buf)
self.ProcRanapLast = Proc.Code
return self._encode_pdu(Proc.send())
#
if self.TRACE_ASN_RANAP:
self._log('TRACE_ASN_RANAP_UL', '\n' + PDU_RANAP.to_asn1())
pdu = PDU_RANAP()
asn_ranap_release()
errcause = None
#
if pdu[0] == 'initiatingMessage':
# RNC-initiated procedure, create it through the dispatcher
try:
Proc = RANAPConlessProcRncDispatcher[pdu[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid connect-less RANAP PDU, initiatingMessage, code %i'\
% pdu[1]['procedureCode'])
# returns a RANAP error ind: protocol, abstract-syntax-error-reject
errcause = ('protocol', 100)
Proc = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=errcause)
else:
# store the procedure, if no error ind
self.ProcRanap[Proc.Code] = Proc
if self.TRACK_PROC_RANAP:
# keep track of the procedure
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu )
#
if Proc.Cont['suc'] is not None or errcause is not None:
# set the last procedure code
self.ProcRanapLast = Proc.Code
# send back any potential response to the RNC
# Proc.send() will take care to clean-up self.Proc
return self._encode_pdu(Proc.send())
else:
# potentially create new RANAP procedures,
# as outcome of the one received
snd = []
for ProcRet in Proc.trigger():
# all those procedures must have been initiated with self.init_ranap_proc()
# hence, they are already set in self.ProcRanap
# and tracked in self._proc
snd.extend( ProcRet.send() )
# set the last procedure code
self.ProcRanapLast = ProcRet.Code
return self._encode_pdu(snd)
#
else:
# CN-initiated procedure, already existing in self.ProcRanap
# transfer the PDU to it
try:
Proc = self.ProcRanap[pdu[1]['procedureCode']]
except:
self._log('ERR', 'invalid RANAP PDU, %s, code %i' % (pdu[0], pdu[1]['procedureCode']))
# returns a RANAP error ind: protocol, message-not-compatible-with-receiver-state
errcause = ('protocol', 99)
Proc = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=errcause)
# process the PDU within the procedure
Proc.recv( pdu )
#
if errcause is not None:
# set the last procedure code
self.ProcRanapLast = Proc.Code
# send back any potential response to the RNC
# Proc.send() will take care to clean-up self.Proc
return self._encode_pdu(Proc.send())
else:
# potentially create new RANAP procedures, as outcome of the one received
snd = []
for ProcRet in Proc.trigger():
# all those procedures must have been initiated with self.init_ranap_proc()
# hence, they are already set in self.Proc
# and tracked in self._proc
snd.extend( ProcRet.send() )
# set the last procedure code
self.ProcRanapLast = ProcRet.Code
return self._encode_pdu(snd)
def _encode_pdu(self, pdus):
ret, cnt = [], 0
if not asn_ranap_acquire():
self._log('ERR', 'unable to acquire the RANAP module')
return cnt
for pdu in pdus:
try:
PDU_RANAP.set_val(pdu)
except Exception as err:
self._log('ERR', 'unable to set the RANAP pdu value')
self._errpdu = pdu
else:
self._log('TRACE_ASN_RANAP_DL', '\n' + PDU_RANAP.to_asn1())
ret.append( PDU_RANAP.to_aper() )
asn_ranap_release()
return ret
def init_hnbap_proc(self, ProcClass, **kw):
"""initialize a CN-initiated HNBAP procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and return the procedure
"""
if ProcClass.Code in self.ProcHnbap:
self._log('ERR', 'a HNBAP procedure %s is already ongoing, unable to start a new one'\
self._log('ERR', 'a HNBAP procedure %s is already ongoing'\
% ProcClass.__name__)
return None
Proc = ProcClass(self)
# store the procedure
self.ProcHnbap[Proc.Code] = Proc
if Proc.Code in HNBAPProcGwDispatcher and Proc.Class == 1:
# store the procedure, which requires a response from the HNB
self.ProcHnbap[Proc.Code] = Proc
if self.TRACK_PROC_HNBAP:
# keep track of the procedure
self._proc.append( Proc )
Proc.encode_pdu('ini', **kw)
return Proc
def init_rua_proc(self, ProcClass, **kw):
"""initialize a CN-initiated RUA procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and return the procedure
"""
if ProcClass.Code in self.ProcHnbap:
self._log('ERR', 'a RUA procedure %s is already ongoing, unable to start a new one'\
% ProcClass.__name__)
return None
Proc = ProcClass(self)
if self.TRACK_PROC_RUA:
# keep track of the procedure
self._proc.append( Proc )
Proc.encode_pdu('ini', **kw)
return Proc
def init_ranap_proc(self, ProcClass, **kw):
"""initialize a CN-initiated RANAP connection-less procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and return the procedure
"""
if not issubclass(ProcClass, RANAPConlessSigProc):
self._log('WNG', 'starting a connection-oriented RANAP procedure '\
'over a RUA connection-less transfer')
if ProcClass.Code in self.ProcRanap:
self._log('ERR', 'a RANAP procedure %s is already ongoing, unable to start a new one'\
% ProcClass.__name__)
return None
Proc = ProcClass(self)
# store the procedure
self.ProcRanap[Proc.Code] = Proc
if self.TRACK_PROC_RANAP:
# keep track of the procedure
self._proc.append( Proc )
Proc.encode_pdu('ini', **kw)
return Proc
def start_hnbap_proc(self, ProcClass, **kw):
"""initialize a HNBAP procedure and send its initiatingMessage PDU over Iuh
"""initialize a CN-initiated HNBAP procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and send the PDU to the
HNB
"""
if not self.is_connected():
self._log('ERR', 'not connected')
@ -411,47 +215,88 @@ class HNBd(SigStack):
Proc = self.init_hnbap_proc(ProcClass, **kw)
if Proc is None:
return 0
self.ProcHnbapLast, cnt = Proc, 0
self.ProcHnbapLast, cnt = Proc.Code, 0
for pdu in Proc.send():
if self.Server.send_hnbap_pdu(self, pdu):
# send_hnbap_pdu() returns the number of bytes sent over the socket
cnt += 1
return cnt
#--------------------------------------------------------------------------#
# handling of RUA procedures
#--------------------------------------------------------------------------#
def process_rua_pdu(self, pdu_rx):
"""process a RUA PDU sent by the HNB
and return a list of RUA PDU(s) to be sent back to it
"""
# WNG: RUA is a symmetric protocol with initatingMessage-only procedures
errcause = None
if pdu_rx[0] != 'initiatingMessage':
self._log('ERR', 'invalid RUA PDU, %s, code %i'\
% (pdu_rx[0], pdu_rx[1]['procedureCode']))
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = self.init_rua_proc(RUAErrorInd, Cause=errcause)
else:
try:
Proc = RUAProcDispatcher[pdu_rx[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid RUA PDU, initiatingMessage, code %i'\
% pdu_rx[1]['procedureCode'])
errcause = ('protocol', 'abstract-syntax-error-reject')
Proc = self.init_rua_proc(RUAErrorInd, Cause=errcause)
if self.TRACK_PROC_RUA:
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu_rx )
if errcause:
self.ProcRuaLast = Proc.Code
return Proc.send()
else:
# trig new RUA procedures, as outcome of the one received
pdu_tx = []
for ProcRet in Proc.trigger():
pdu_tx.extend( ProcRet.send() )
self.ProcRuaLast = ProcRet.Code
return pdu_tx
def init_rua_proc(self, ProcClass, **kw):
"""initialize a CN-initiated RUA procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and return the procedure
"""
Proc = ProcClass(self)
if self.TRACK_PROC_RUA:
# keep track of the procedure
self._proc.append( Proc )
Proc.encode_pdu('ini', **kw)
return Proc
def start_rua_proc(self, ProcClass, **kw):
"""initialize a RUA procedure and send its initiatingMessage PDU over Iuh
"""initialize a CN-initiated RUA procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and send the PDU to the
HNB
"""
if not self.is_connected():
self._log('ERR', 'not connected')
return 0
Proc = self.init_rua_proc(ProcClass, **kw)
if Proc is None:
return 0
self.ProcRuaLast, cnt = Proc, 0
self.ProcRuaLast, cnt = Proc.Code, 0
for pdu in Proc.send():
if self.Server.send_rua_pdu(self, pdu):
# send_rua_pdu() returns the number of bytes sent over the socket
cnt += 1
return cnt
def start_ranap_proc(self, ProcClass, **kw):
"""initialize a RANAP connection-less procedure and send its initiatingMessage PDU
over RUA / Iuh
"""
if not self.is_connected():
self._log('ERR', 'not connected')
return 0
#
Proc = self.init_ranap_proc(ProcClass, **kw)
if Proc is None:
return
self.ProcRanapLast, cnt, pdus = Proc, 0, []
#
# encode the RANAP PDU(s)
#--------------------------------------------------------------------------#
# handling of RANAP procedures
#--------------------------------------------------------------------------#
def _encode_ranap_pdu(self, pdus):
ret = []
if not asn_ranap_acquire():
self._log('ERR', 'unable to acquire the RANAP module')
return 0
for pdu in Proc.send():
return ret
for pdu in pdus:
try:
PDU_RANAP.set_val(pdu)
except Exception as err:
@ -460,10 +305,122 @@ class HNBd(SigStack):
else:
if self.TRACE_ASN_RANAP:
self._log('TRACE_ASN_RANAP_DL', '\n' + PDU_RANAP.to_asn1())
pdus.append( PDU_RANAP.to_aper() )
ret.append( PDU_RANAP.to_aper() )
asn_ranap_release()
return ret
def process_ranap(self, buf):
"""process a RANAP PDU buffer sent by the HNB in connection-less transfer
and return a list of RANAP PDU buffer(s) to be sent back to it
"""
# decode the RANAP PDU
if not asn_ranap_acquire():
self._log('ERR', 'unable to acquire the RANAP module')
return []
try:
PDU_RANAP.from_aper(buf)
except:
asn_ranap_release()
self._log('WNG', 'invalid RANAP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
# error cause: protocol, transfer-syntax-error
Proc = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=('protocol', 97))
Proc.recv(buf)
self.ProcRanapLast = Proc.Code
return self._encode_ranap_pdu(Proc.send())
#
if self.TRACE_ASN_RANAP:
self._log('TRACE_ASN_RANAP_UL', '\n' + PDU_RANAP.to_asn1())
pdu_rx = PDU_RANAP()
asn_ranap_release()
#
for buf in pdus:
errcause = None
if pdu_rx[0] == 'initiatingMessage':
# RNC-initiated procedure, instantiate it
try:
Proc = RANAPConlessProcRncDispatcher[pdu_rx[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid connect-less RANAP PDU, initiatingMessage, code %i'\
% pdu_rx[1]['procedureCode'])
# error cause: protocol, abstract-syntax-error-reject
errcause = ('protocol', 100)
Proc = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=errcause)
else:
if self.TRACK_PROC_RANAP:
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu_rx )
if Proc.Class == 2 and Proc.errcause:
Err = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=Proc.errcause)
self.ProcRanapLast = Err.Code
return self._encode_ranap_pdu(Err.send())
elif Proc.Class == 1 or errcause:
self.ProcRanapLast = Proc.Code
return self._encode_ranap_pdu(Proc.send())
else:
pdu_tx = []
for ProcRet in Proc.trigger():
pdu_tx.extend( ProcRet.send() )
self.ProcRanapLast = ProcRet.Code
return self._encode_ranap_pdu(pdu_tx)
#
else:
# CN-initiated procedure, transfer the PDU to it
try:
Proc = self.ProcRanap[pdu_rx[1]['procedureCode']]
except:
self._log('ERR', 'invalid connect-less RANAP PDU, %s, code %i'\
% (pdu_rx[0], pdu_rx[1]['procedureCode']))
# error cause: protocol, message-not-compatible-with-receiver-state
errcause = ('protocol', 99)
Proc = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=errcause)
# process the PDU within the procedure
Proc.recv( pdu_rx )
if Proc.errcause:
Err = self.init_ranap_proc(RANAPErrorIndConlessCN, Cause=Proc.errcause)
self.ProcRanapLast = Err.Code
return self._encode_ranap_pdu(Err.send())
elif errcause:
self.ProcRanapLast = Proc.Code
return self._encode_ranap_pdu(Proc.send())
else:
pdu_tx = []
for ProcRet in Proc.trigger():
pdu_tx.extend( ProcRet.send() )
self.ProcRanapLast = ProcRet.Code
return self._encode_ranap_pdu(pdu_tx)
def init_ranap_proc(self, ProcClass, **kw):
"""initialize a CN-initiated RANAP connection-less procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and return the procedure
"""
if not issubclass(ProcClass, RANAPConlessSigProc):
self._log('WNG', 'starting an invalid procedure over a RUA connection-less transfer')
if ProcClass.Code in self.ProcRanap:
self._log('ERR', 'a RANAP procedure %s is already ongoing' % ProcClass.__name__)
return None
Proc = ProcClass(self)
if Proc.Code in RANAPConlessProcCnDispacther and Proc.Class == 1:
# store the procedure, which requires a response from the HNB
self.ProcRanap[Proc.Code] = Proc
if self.TRACK_PROC_RANAP:
# keep track of the procedure
self._proc.append( Proc )
Proc.encode_pdu('ini', **kw)
return Proc
def start_ranap_proc(self, ProcClass, **kw):
"""initialize a CN-initiated RANAP connection-less procedure of class `ProcClass',
encode the initiatingMessage PDU with given **kw and send the PDU to the HNB
"""
if not self.is_connected():
self._log('ERR', 'not connected')
return 0
Proc = self.init_ranap_proc(ProcClass, **kw)
if Proc is None:
return 0
self.ProcRanapLast, cnt = Proc.Code, 0
for buf in self._encode_ranap_pdu(Proc.send()):
cnt += self.start_rua_proc(RUAConnectlessTransfer, RANAP_Message=buf)
return cnt
@ -508,7 +465,7 @@ class HNBd(SigStack):
self._log('WNG', 'no UE with IuPS context-id %i to unset' % ctx_id)
#--------------------------------------------------------------------------#
# handling of RANAP connection-less signaling procedures
# CN-initiated RANAP connection-less signaling procedures
#--------------------------------------------------------------------------#
def send_error_ind(self, cause, **IEs):
@ -521,7 +478,7 @@ class HNBd(SigStack):
# send to the RNC in connection-less signaling
ret = self.start_ranap_proc(RANAPErrorIndConlessCN, **IEs)
if not ret:
self._log('ERR', 'send_error_ind: invalid IEs')
self._log('ERR', 'send_error_ind: error')
return True if ret else False
def reset(self, dom, cause=('misc', 115), **IEs):
@ -548,7 +505,7 @@ class HNBd(SigStack):
# send to the RNC in connection-less signaling
ret = self.start_ranap_proc(RANAPResetCN, **IEs)
if not ret:
self._log('ERR', 'reset: invalid IEs')
self._log('ERR', 'reset: error')
return True if ret else False
def reset_resource(self, dom, reslist=[], cause=('misc', 115), **IEs):
@ -595,7 +552,7 @@ class HNBd(SigStack):
# send to the RNC in connection-less signaling
ret = self.start_ranap_proc(RANAPResetResourceCN, **IEs)
if not ret:
self._log('ERR', 'reset: invalid IEs')
self._log('ERR', 'reset: error')
return True if ret else False
def page(self, **IEs):
@ -605,5 +562,6 @@ class HNBd(SigStack):
"""
ret = self.start_ranap_proc(RANAPPaging, **IEs)
if not ret:
self._log('ERR', 'page: invalid IEs')
self._log('ERR', 'page: error')
return True if ret else False

View File

@ -52,9 +52,13 @@ class UEd(SigStack):
TRACE_NAS_CS = False
# to log UE NAS GMM / SM for all UE
TRACE_NAS_PS = False
# to log UE NAS encrypted EMM / EMM / ESM for all UE
TRACE_NAS_EMMENC = False
# to log UE LTE NAS (potentially) encrypted EMM / ESM for all UE
TRACE_NAS_EPS_ENC = False
# to log UE LTE NAS clear-text EMM / ESM for all UE
TRACE_NAS_EPS = False
# to log UE LTE NAS containing SMS for all UE
TRACE_NAS_EPS_SMS = False
#--------------------------------------------------------------------------#
# UE global informations
@ -66,6 +70,8 @@ class UEd(SigStack):
IMEISV = None
# capabilities
Cap = {}
# security capabilities
SecCap = {RAT_GERA: set(), RAT_UTRA: set(), RAT_EUTRA: set()}
# temporary identities (TMSI / PTMSI are uint32)
TMSI = None
PTMSI = None
@ -111,23 +117,43 @@ class UEd(SigStack):
self.TMSI = kw['tmsi']
elif 'ptmsi' in kw:
self.PTMSI = kw['ptmsi']
#
if 'config' in kw:
self.set_config(kw['config'])
elif 'mtmsi' in kw:
self.MTMSI = kw['mtmsi']
#
# set handler for IuCS, IuPS and S1 links
self.IuCS = UEIuCSd(self)
self.IuPS = UEIuPSd(self)
self.S1 = UES1d(self)
#
if 'config' in kw:
self.set_config(kw['config'])
def set_config(self, config):
self.MSISDN = config['MSISDN']
self.IPAddr = config['IPAddr']
self.USIM = config['USIM']
#
self.IuPS.SM.PDP = {}
# TODO: handle config for PDP networks
#
self.S1.ESM.PDN = {}
for apn, pdntype, ipaddr in config['PDN']:
#Server.ConfigPDN provides the DNS servers for each APN
#UE.S1.ESM.PDNConfig provides the default QoS for each APN
if apn not in self.Server.ConfigPDN:
self._log('WNG', 'unable to configure PDN connectivity for APN %s, no DNS servers'\
% apn)
else:
self.S1.ESM.PDN = {apn: {'IP' : (pdntype, ipaddr),
'DNS': self.Server.ConfigPDN[apn]}}
if apn not in self.S1.ESM.PDNConfig:
self._log('WNG', 'unable to configure PDN connectivity for APN %s, '\
'no S1 QoS parameters' % apn)
else:
self.S1.ESM.PDN[apn].update( self.S1.ESM.PDNConfig[apn] )
def set_ran(self, ran, ctx_id):
def set_ran(self, ran, ctx_id, sid=None):
# UE going connected
if ran.__class__.__name__[:3] == 'HNB':
if ran.__class__.__name__ == 'HNBd':
#
if self.S1.is_connected():
# error: already linked with another ran
@ -152,7 +178,7 @@ class UEd(SigStack):
# error: already linked with another HNB
raise(CorenetErr('UE already connected through another IuPS link'))
#
elif ran.__class__.__name__[:3] == 'ENB':
elif ran.__class__.__name__ == 'ENBd':
#
if self.IuCS.is_connected() or self.IuPS.is_connected():
# error: already linked with another ran
@ -161,9 +187,9 @@ class UEd(SigStack):
# S1 stack
if not self.S1.is_connected():
self.S1.set_ran(ran)
self.S1.set_ctx(ctx_id)
self.S1.set_ctx(ctx_id, sid)
elif self.S1.ENB == ran:
self.S1.set_ctx(ctx_id)
self.S1.set_ctx(ctx_id, sid)
else:
# error: already linked with another ENB
raise(CorenetErr('UE already connected through another S1 link'))
@ -186,29 +212,85 @@ class UEd(SigStack):
self.S1.unset_ctx()
del self.RAT
def merge_cs_handler(self, iucs):
if self.IuCS is not None and self.IuCS.MM.state != UEMMd.state:
return False
else:
self.IuCS = iucs
iucs.UE = self
iucs.MM.UE = self
iucs.CC.UE = self
iucs.SMS.UE = self
iucs.SS.UE = self
return True
def merge_cs_handler(self, iucsd):
if self.IuCS is not None:
if self.IuCS.MM.state == 'ACTIVE':
self._log('WNG', 'unable to merge IuCS handler')
return False
else:
self._log('INF', 'merging IuCS handler')
# prepend passed proc into s1d
iucsd._proc = self.IuCS._proc + iucsd._proc
iucsd.MM._proc = self.IuCS.MM._proc + iucsd.MM._proc
iucsd.CC._proc = self.IuCS.CC._proc + iucsd.CC._proc
iucsd.SMS._proc = self.IuCS.SMS._proc + iucsd.SMS._proc
iucsd.SS._proc = self.IuCS.SS._proc + iucsd.SS._proc
# merge security contexts
for cksn in range(8):
if cksn in self.IuCS.SEC and cksn not in iucsd.SEC:
iucsd.SEC[cksn] = self.IuCS.SEC[cksn]
# transfer UE's reference
self.IuCS = iucs
iucs.UE = self
iucs.MM.UE = self
iucs.CC.UE = self
iucs.SMS.UE = self
iucs.SS.UE = self
return True
def merge_ps_handler(self, iups):
if self.IuPS is not None and self.IuPS.GMM.state != UEGMMd.state:
print('iups: ', iups, iups.GMM, iups.GMM.state)
print('self.IuPS: ', self.IuPS, self.IuPS.GMM, self.IuPS.GMM.state)
return False
else:
self.IuPS = iups
iups.UE = self
iups.GMM.UE = self
iups.SM.UE = self
return True
def merge_ps_handler(self, iupsd):
if self.IuPS is not None:
if self.IuPS.GMM.state == 'ACTIVE':
self._log('WNG', 'unable to merge IuPS handler')
return False
else:
self._log('INF', 'merging IuPS handler')
# prepend passed proc into s1d
iupsd._proc = self.IuPS._proc + iupsd._proc
iupsd.GMM._proc = self.IuPS.GMM._proc + iupsd.GMM._proc
iupsd.SM._proc = self.IuPS.SM._proc + iupsd.SM._proc
# merge security contexts
for cksn in range(8):
if cksn in self.IuPS.SEC and cksn not in iupsd.SEC:
iupsd.SEC[cksn] = self.IuPS.SEC[cksn]
# merge PDP contexts
for ctx in range(16):
if ctx in self.IuPS.PDP and ctx not in iupsd.PDP:
iupsd.PDP[ksi] = self.IuPS.PDP[ctx]
# transfer UE's reference
self.IuPS = iups
iups.UE = self
iups.GMM.UE = self
iups.SM.UE = self
return True
def merge_eps_handler(self, s1d):
if self.S1 is not None:
if self.S1.EMM.state == 'ACTIVE':
self._log('WNG', 'unable to merge S1 handler')
return False
else:
self._log('INF', 'merging S1 handler')
# prepend passed proc into s1d
s1d._proc = self.S1._proc + s1d._proc
s1d.EMM._proc = self.S1.EMM._proc + s1d.EMM._proc
s1d.ESM._proc = self.S1.ESM._proc + s1d.ESM._proc
s1d.SMS._proc = self.S1.SMS._proc + s1d.SMS._proc
# merge security contexts
for ksi in range(16):
if ksi in self.S1.SEC and ksi not in s1d.SEC:
s1d.SEC[ksi] = self.S1.SEC[ksi]
# merge PDP contexts
for ctx in range(16):
if ctx in self.S1.PDP and ctx not in s1d.PDP:
s1d.PDP[ctx] = self.S1.PDP[ctx]
# transfer UE's reference
self.S1 = s1d
s1d.UE = self
s1d.EMM.UE = self
s1d.ESM.UE = self
s1d.SMS.UE = self
return True
#--------------------------------------------------------------------------#
# UE identity
@ -244,10 +326,11 @@ class UEd(SigStack):
self.PTMSI = ident
elif ident != self.PTMSI:
self._log('WNG', 'incorrect P-TMSI, %s instead of %s' % (ident, self.PTMSI))
elif dom == 'EPS':
elif idtype == 6:
if dom == 'EPS':
if self.MTMSI is None:
self.MTMSI = ident
elif ident != self.MTMSI:
self.MTMSI = ident[3]
elif ident[3] != self.MTMSI:
self._log('WNG', 'incorrect M-TMSI, %s instead of %s' % (ident, self.MTMSI))
else:
self._log('INF', 'unhandled identity, type %i, ident %s' % (idtype, ident))
@ -299,24 +382,28 @@ class UEd(SigStack):
def set_plmn(self, plmn):
if plmn != self.PLMN:
self.PLMN = plmn
self._log('INF', 'locate to PLMN %s' % self.PLMN)
self._log('INF', 'locate on PLMN %s' % self.PLMN)
def set_lac(self, lac):
if lac != self.LAC:
self.LAC = lac
self._log('INF', 'locate to LAC %.4x' % self.LAC)
self._log('INF', 'locate on LAC %.4x' % self.LAC)
def set_rac(self, rac):
if rac != self.RAC:
self.RAC = rac
self._log('INF', 'routing to RAC %.2x' % self.RAC)
self._log('INF', 'route on RAC %.2x' % self.RAC)
def set_tac(self, tac):
if tac != self.TAC:
self.TAC = tac
# TBC
self._log('INF', 'tracking to TAC')
self._log('INF', 'track on TAC %.4x' % self.TAC)
def set_lai(self, plmn, lac):
self.set_plmn(plmn)
self.set_lac(lac)
def set_tai(self, plmn, tac):
self.set_plmn(plmn)
self.set_tac(tac)

View File

@ -50,7 +50,7 @@ class UEIuSigStack(SigStack):
# reference to the UEd
UE = None
# reference to the HNBd
# reference to the RNCd / HNBd
RNC = None
# core network domain (CS or PS)
@ -59,12 +59,12 @@ class UEIuSigStack(SigStack):
# for pure RANAP procedure (no NAS trafic, neither RAB-oriented stuff)
# should we page the UE to run the procedure successfully when UE is idle
RANAP_FORCE_PAGE = False
def _log(self, logtype, msg):
self.UE._log(logtype, '[%s: %3i] %s' % (self.__class__.__name__, self.CtxId, msg))
def __init__(self, ued, hnbd, ctx_id):
def __init__(self, ued, rncd, ctx_id):
self.UE = ued
self.Server = ued.Server
if self.DOM == 'PS':
@ -78,24 +78,21 @@ class UEIuSigStack(SigStack):
# list of tracked procedures (requires TRACK_PROC = True)
self._proc = []
#
# RANAP callback for NAS stacks
self.RanapTx = None
#
# dict of available 2G / 3G security contexts, indexed by CKSN
# and current CKSN in use
self.SEC = {}
self.reset_sec_ctx()
#
self.connected = Event()
if hnbd is not None:
self.set_ran(hnbd)
if rncd is not None:
self.set_ran(rncd)
self.set_ctx(ctx_id)
else:
self.unset_ctx()
def set_ran(self, hnbd):
def set_ran(self, rncd):
self.SEC['CKSN'] = None
self.RNC = hnbd
self.RNC = rncd
self.connected.set()
def unset_ran(self):
@ -103,10 +100,10 @@ class UEIuSigStack(SigStack):
self.SEC['CKSN'] = None
self.connected.clear()
def set_ran_unconnected(self, hnbd):
def set_ran_unconnected(self, rncd):
# required for paging
self.SEC['CKSN'] = None
self.RNC = hnbd
self.RNC = rncd
def unset_ran_unconnected(self):
# required for paging
@ -124,115 +121,10 @@ class UEIuSigStack(SigStack):
self.CtxId = -1
#--------------------------------------------------------------------------#
# RANAP-related methods
# handling of RANAP procedures
#--------------------------------------------------------------------------#
def process_ranap(self, buf):
"""process a RANAP PDU buffer sent by the RNC handling the UE connection
and return a list with RANAP PDU(s) to be sent back to the RNC
"""
# decode the RANAP PDU
if not asn_ranap_acquire():
self._log('ERR', 'unable to acquire the RANAP module')
return []
try:
PDU_RANAP.from_aper(buf)
except:
# unable to decode APER-encoded buffer
self._log('WNG', 'invalid RANAP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
# returns a RANAP error ind: protocol, transfer-syntax-error
Proc = self.init_ranap_proc(RANAPErrorIndCN, Cause=('protocol', 97))
if Proc is None:
return []
else:
Proc.recv(buf)
self.ProcLast = Proc.Code
return Proc.send()
#
if self.DOM == 'CS' and self.UE.TRACE_ASN_RANAP_CS:
self._log('TRACE_ASN_RANAP_CS_UL', '\n' + PDU_RANAP.to_asn1())
elif self.DOM == 'PS' and self.UE.TRACE_ASN_RANAP_PS:
self._log('TRACE_ASN_RANAP_PS_UL', '\n' + PDU_RANAP.to_asn1())
pdu = PDU_RANAP()
asn_ranap_release()
errcause = None
#
if pdu[0] == 'initiatingMessage':
# RNC-initiated procedure, create it through the dispatcher
try:
Proc = RANAPProcRncDispatcher[pdu[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid RANAP PDU, initiatingMessage, code %i'\
% pdu[1]['procedureCode'])
# returns a RANAP error ind: protocol, abstract-syntax-error-reject
errcause = ('protocol', 100)
Proc = self.init_ranap_proc(RANAPErrorIndCN, Cause=errcause)
if Proc is None:
return []
else:
# store the procedure, if no error ind
self.Proc[Proc.Code] = Proc
if self.TRACK_PROC:
# keep track of the procedure
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu )
#
if Proc.Cont['suc'] is not None or errcause is not None:
# procedure requires a response
# set the last procedure code in the RNC handler
self.ProcLast = Proc.Code
# send back any potential response to the RNC
# Proc.send() will take care to clean-up self.Proc
return self._encode_pdu(Proc.send())
else:
# potentially create new RANAP procedures,
# as outcome of the one received
snd = []
for ProcRet in Proc.trigger():
# all those procedures must have been initiated with self.init_ranap_proc()
# hence, they are already set in self.Proc
# and tracked in self._proc
snd.extend( ProcRet.send() )
# set the last procedure code
self.ProcLast = ProcRet.Code
return self._encode_pdu(snd)
#
else:
# CN-initiated procedure, already existing in self.Proc
# transfer the PDU to it
try:
Proc = self.Proc[pdu[1]['procedureCode']]
except:
self._log('ERR', 'invalid RANAP PDU, %s, code %i' % (pdu[0], pdu[1]['procedureCode']))
# returns a RANAP error ind: protocol, message-not-compatible-with-receiver-state
errcause = ('protocol', 99)
Proc = self.init_ranap_proc(RANAPErrorIndCN, Cause=errcause)
if Proc is None:
return []
# process the PDU within the procedure
Proc.recv( pdu )
#
if errcause is not None:
# set the last procedure code
self.ProcLast = Proc.Code
# send back any potential response to the RNC
# Proc.send() will take care to clean-up self.Proc
return self._encode_pdu(Proc.send())
else:
# potentially create new RANAP procedures, as outcome of the one received
snd = []
for ProcRet in Proc.trigger():
# all those procedures must have been initiated with self.init_ranap_proc()
# hence, they are already set in self.Proc
# and tracked in self._proc
snd.extend( ProcRet.send() )
# set the last procedure code
self.ProcLast = ProcRet.Code
return self._encode_pdu(snd)
def _encode_pdu(self, pdus):
def _encode_ranap_pdu(self, pdus):
ret = []
if not asn_ranap_acquire():
self._log('ERR', 'unable to acquire the RANAP module')
@ -252,62 +144,160 @@ class UEIuSigStack(SigStack):
asn_ranap_release()
return ret
def process_ranap(self, buf):
"""process a RANAP PDU buffer sent by the RNC for a connected UE
and return a list of RANAP PDU buffer(s) to be sent back to it
"""
# decode the RANAP PDU
if not asn_ranap_acquire():
self._log('ERR', 'unable to acquire the RANAP module')
return []
try:
PDU_RANAP.from_aper(buf)
except:
asn_ranap_release()
self._log('WNG', 'invalid RANAP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
# error cause: protocol, transfer-syntax-error
Proc = self.init_ranap_proc(RANAPErrorIndCN, Cause=('protocol', 97))
Proc.recv(buf)
self.ProcLast = Proc.Code
return self._encode_ranap_pdu(Proc.send())
#
if self.DOM == 'CS' and self.UE.TRACE_ASN_RANAP_CS:
self._log('TRACE_ASN_RANAP_CS_UL', '\n' + PDU_RANAP.to_asn1())
elif self.DOM == 'PS' and self.UE.TRACE_ASN_RANAP_PS:
self._log('TRACE_ASN_RANAP_PS_UL', '\n' + PDU_RANAP.to_asn1())
pdu_rx = PDU_RANAP()
asn_ranap_release()
#
errcause = None
if pdu_rx[0] == 'initiatingMessage':
# RNC-initiated procedure, instantiate it
try:
Proc = RANAPProcRncDispatcher[pdu_rx[1]['procedureCode']](self)
except:
self._log('ERR', 'invalid RANAP PDU, initiatingMessage, code %i'\
% pdu_rx[1]['procedureCode'])
# error cause: protocol, abstract-syntax-error-reject
errcause = ('protocol', 100)
Proc = self.init_ranap_proc(RANAPErrorIndCN, Cause=errcause)
if not Proc:
return []
else:
if self.TRACK_PROC:
self._proc.append( Proc )
# process the PDU within the procedure
Proc.recv( pdu_rx )
if Proc.Class == 2 and Proc.errcause:
Err = self.init_ranap_proc(RANAPErrorIndCN, Cause=Proc.errcause)
self.ProcLast = Err.Code
return self._encode_ranap_pdu(Err.send())
elif Proc.Class == 1 or errcause:
self.ProcLast = Proc.Code
return self._encode_ranap_pdu(Proc.send())
else:
pdu_tx = []
for ProcRet in Proc.trigger():
pdu_tx.extend( ProcRet.send() )
self.ProcLast = ProcRet.Code
return self._encode_ranap_pdu(pdu_tx)
#
else:
# CN-initiated procedure, transfer the PDU to it
try:
Proc = self.Proc[pdu_rx[1]['procedureCode']]
except:
self._log('ERR', 'invalid RANAP PDU, %s, code %i'\
% (pdu_rx[0], pdu_rx[1]['procedureCode']))
# error cause: protocol, message-not-compatible-with-receiver-state
errcause = ('protocol', 99)
Proc = self.init_ranap_proc(RANAPErrorIndCN, Cause=errcause)
if not Proc:
return []
# process the PDU within the procedure
Proc.recv( pdu_rx )
if Proc.errcause:
Err = self.init_ranap_proc(RANAPErrorIndCN, Cause=Proc.errcause)
self.ProcLast = Err.Code
return self._encode_ranap_pdu(Err.send())
elif errcause:
self.ProcLast = Proc.Code
return self._encode_ranap_pdu(Proc.send())
else:
pdu_tx = []
for ProcRet in Proc.trigger():
pdu_tx.extend( ProcRet.send() )
self.ProcLast = ProcRet.Code
return self._encode_ranap_pdu(pdu_tx)
def init_ranap_proc(self, ProcClass, **kw):
"""initialize a CN-initiated RANAP procedure of class `ProcClass',
"""initialize a CN-initiated RANAP procedure of class `ProcClass' for a connected UE,
encode the initiatingMessage PDU with given **kw and return the procedure
"""
if not issubclass(ProcClass, RANAPSigProc):
self._log('WNG', 'starting a connection-less RANAP procedure '\
'over a RUA connection-oriented transfer')
self._log('WNG', 'starting an invalid procedure over a RUA connection-oriented transfer')
if ProcClass.Code in self.Proc:
self._log('ERR', 'a RANAP procedure %s is already ongoing, unable to start a new one'\
% ProcClass.__name__)
self._log('ERR', 'a RANAP procedure %s is already ongoing' % ProcClass.__name__)
return None
try:
Proc = ProcClass(self)
except:
# self has no active Iu link
self._log('ERR', 'no active Iu link to start a RANAP procedure %s' % ProcClass.__name__)
# no active Iu link
self._log('ERR', 'no active Iu link to initialize the RANAP procedure %s'\
% ProcClass.__name__)
return None
# store the procedure
self.Proc[Proc.Code] = Proc
if Proc.Code in RANAPProcCnDispatcher and Proc.Class == 1:
# store the procedure, which requires a response from the RNC
self.Proc[Proc.Code] = Proc
if self.TRACK_PROC:
# keep track of the procedure
self._proc.append( Proc )
Proc.encode_pdu('ini', **kw)
return Proc
def clear(self):
# clears all running RANAP CS/PS procedures
for code in self.Proc.keys():
self.Proc[code].abort()
for Proc in self.Proc.values():
Proc.abort()
#--------------------------------------------------------------------------#
# NAS-related methods
#--------------------------------------------------------------------------#
def ret_ranap_dt(self, NAS_PDU):
# return a RANAPDirectTransfer with the NAS PDU to be sent
def ret_ranap_dt(self, NasTx, sapi=0):
"""returns a RANAPDirectTransfer procedure initialize with the NAS PDU to
be sent
"""
if self.DOM == 'CS' and self.UE.TRACE_NAS_CS:
self._log('TRACE_NAS_CS_DL', '\n' + NasTx.show())
elif self.DOM == 'PS' and self.UE.TRACE_NAS_PS:
self._log('TRACE_NAS_PS_DL', '\n' + NasTx.show())
try:
naspdu = NAS_PDU.to_bytes()
naspdu = NasTx.to_bytes()
except Exception as err:
self._log('ERR', 'unable to encode downlink NAS PDU: %r' % err)
self._log('ERR', 'unable to encode the NAS PDU: %r' % err)
return []
else:
if sapi == 3:
sapi = 'sapi-3'
else:
sapi = 'sapi-0'
RanapProc = self.init_ranap_proc(RANAPDirectTransferCN,
NAS_PDU=naspdu,
SAPI='sapi-0')
SAPI=sapi)
if RanapProc:
return [RanapProc]
return []
else:
return []
def trigger_nas(self, RanapProc):
# this is used by IuCS/PS RANAP procedures to recall an ongoing NAS procedure
# e.g. SMC to recall a LUR or Attach
if RanapProc._cb is None:
# no callback set, this is actually useless
return []
NasProc = RanapProc._cb
NasTx = NasProc.postprocess(RanapProc)
return self._ret_ranap_proc(NasTx)
return NasProc.postprocess(RanapProc)
#--------------------------------------------------------------------------#
# SMC and security-related methods
@ -339,7 +329,7 @@ class UEIuSigStack(SigStack):
# no security ctxt available at all
self._log('WNG', 'no security context available, using SMC_DUMMY')
secctx = self.SMC_DUMMY
# prepare the kwargs for encoding the SMC encoding
# prepare the IEs for encoding the SMC
IEs = {}
if self.SMC_UIA is not None:
IEs['IntegrityProtectionInformation'] = \
@ -358,10 +348,11 @@ class UEIuSigStack(SigStack):
return IEs
def _get_any_cksn(self):
if self.SEC['CKSN'] is not None:
try:
return self.SEC[self.SEC['CKSN']]
except:
cur = self.SEC['CKSN']
if cur is not None:
if cur in self.SEC:
return cur
else:
# given CKSN not available anymore
self.SEC['CKSN'] = None
#
@ -377,10 +368,9 @@ class UEIuSigStack(SigStack):
return i
# all CKSN have been used, clear all of them except the current one
cur = self.SEC['CKSN']
l = list(range(0, 7))
if cur is not None:
l.remove(cur)
[self.SEC.__delitem__(i) for i in l]
for i in range(0, 7):
if i != cur:
del self.SEC[i]
if cur == 0:
return 1
else:
@ -389,23 +379,24 @@ class UEIuSigStack(SigStack):
def set_sec_ctx(self, cksn, ctx, vect):
if ctx == 3:
# 3G sec ctx
ctx = {'VEC': vect,
'CTX': ctx,
'CK' : vect[3],
'IK' : vect[4],
'UEA': self.SMC_UEA,
'UIA': self.SMC_UIA}
secctx = {'VEC': vect,
'CTX': ctx,
'CK' : vect[3],
'IK' : vect[4],
'UEA': self.SMC_UEA,
'UIA': self.SMC_UIA}
else:
# ctx == 2, 2G sec ctx
# convert 2G Kc to 3G Ck, Ik
CK, IK = conv_C4(vect[2]), conv_C5(vect[2])
ctx = {'VEC': vect,
'CTX': ctx,
'CK' : CK,
'IK' : IK,
'UEA': self.SMC_UEA,
'UIA': self.SMC_UIA}
self.SEC[cksn] = ctx
secctx = {'VEC': vect,
'CTX': ctx,
'Kc' : vect[2],
'CK' : CK,
'IK' : IK,
'UEA': self.SMC_UEA,
'UIA': self.SMC_UIA}
self.SEC[cksn] = secctx
self.SEC['CKSN'] = cksn
#--------------------------------------------------------------------------#
@ -413,7 +404,7 @@ class UEIuSigStack(SigStack):
#--------------------------------------------------------------------------#
def _send_to_rnc(self, buf):
if self.RNC is None:
if not self.RNC:
self._log('WNG', 'no RNC set, unable to send data')
return False
elif self.CtxId < 0:
@ -426,33 +417,18 @@ class UEIuSigStack(SigStack):
CN_DomainIndicator=self._cndomind)
return True if ret else False
def _send_to_rnc_nasdt(self, naspdu, sapi=0):
# prepare the RANAPDirectTransferCN procedure
if sapi == 3:
sapi = 'sapi-3'
else:
sapi = 'sapi-0'
RanapProc = self.init_ranap_proc(RANAPDirectTransferCN,
NAS_PDU=naspdu,
SAPI=sapi)
if not RanapProc:
return False
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', '_send_to_rnc_nasdt: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to RNC in connected mode
return self._send_to_rnc(pdu)
def _send_to_ue(self, naspdu, sapi=0):
if not self.page_block():
self._log('ERR', 'unable to page')
return False
return self._send_to_rnc_nasdt(naspdu, sapi)
def _send_to_rnc_ranap(self, RanapProcs):
ret = []
for RanapProc in RanapProcs:
# encode the RANAP PDU and send it over RUA
pdus = self._encode_ranap_pdu(RanapProc.send())
if not pdus:
self._log('ERR', '_send_to_rnc_ranap: %s, invalid RANAP IEs' % RanapProc.Name)
return False
self.ProcLast = RanapProc.Code
for pdu in pdus:
ret.append( self._send_to_rnc(pdu) )
return all(ret)
def release(self, cause=('nAS', 83)):
"""release the Iu link with the given RANAP cause
@ -466,15 +442,7 @@ class UEIuSigStack(SigStack):
if not RanapProc:
return False
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'release: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to RNC in connected mode
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
def send_error_ind(self, cause, **IEs):
"""start a RANAPErrorIndCN with the given RANAP cause
@ -496,20 +464,7 @@ class UEIuSigStack(SigStack):
if not RanapProc:
return False
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'send_error_ind: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the Iu handler and remove from the
# procedure stack
self.ProcLast = RanapProc.Code
try:
del self.Proc[RanapProc.Code]
except:
pass
# send to the RNC in connection-oriented signaling
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
def send_common_id(self, **IEs):
"""start a RANAPCommonID with the UE's IMSI
@ -533,15 +488,7 @@ class UEIuSigStack(SigStack):
if not RanapProc:
return False
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'send_common_id: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to the RNC in connection-oriented signaling
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
def invoke_trace(self, traceref, **IEs):
"""start a RANAPCNInvokeTrace with a given trace reference (2 or 3 bytes)
@ -565,15 +512,7 @@ class UEIuSigStack(SigStack):
# required for the logging within the procedure
RanapProc.TraceReference = traceref
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'invoke_trace: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to the RNC in connection-oriented signaling
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
def deactivate_trace(self, traceref, triggerid=None):
"""start a RANAPCNDeactivateTrace with a given trace reference (2 or 3 bytes)
@ -598,15 +537,7 @@ class UEIuSigStack(SigStack):
# required for the logging within the procedure
RanapProc.TraceReference = traceref
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'deactivate_trace: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to the RNC in connection-oriented signaling
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
def report_loc_ctrl(self, reqtype={'event':'direct', 'reportArea':'service-area', 'accuracyCode':0},
**IEs):
@ -632,15 +563,7 @@ class UEIuSigStack(SigStack):
# required for the logging within the procedure
RanapProc.RequestType = reqtype
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'report_loc_ctrl: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to the RNC in connection-oriented signaling
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
def request_loc_data(self, reqtype={'requestedLocationRelatedDataType': 'decipheringKeysUEBasedOTDOA'},
**IEs):
@ -665,15 +588,7 @@ class UEIuSigStack(SigStack):
if not RanapProc:
return False
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'request_loc_data: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to the RNC in connection-oriented signaling
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
def report_data_vol(self, rabidlist):
"""start a RANAPDataVolumeReport for the given list of RAB IDs (uint8)
@ -701,15 +616,7 @@ class UEIuSigStack(SigStack):
if not RanapProc:
return False
# encode the RANAP PDU and send it over RUA
pdu = self._encode_pdu(RanapProc.send())
if not pdu:
self._log('ERR', 'report_data_vol: invalid IEs')
return False
pdu = pdu[0]
# set the last procedure code in the RNC handler
self.ProcLast = RanapProc.Code
# send to the RNC in connection-oriented signaling
return self._send_to_rnc(pdu)
return self._send_to_rnc_ranap([RanapProc])
#--------------------------------------------------------------------------#
# to send arbitrary NAS buffers to the UE
@ -722,29 +629,36 @@ class UEIuSigStack(SigStack):
return False
#
self.RX_HOOK = rx_hook
if isinstance(naspdu, bytes_types):
if not self._send_to_rnc_nasdt(naspdu, sapi=sapi):
del self.RX_HOOK
return False
else:
self._log('INF', 'send_nas_raw: 0x%s' % hexlify(naspdu).decode('ascii'))
sleep(wait_t)
if sapi == 3:
sapi = 'sapi-3'
else:
sapi = 'sapi-0'
#
elif isinstance(naspdu, (tuple, list)):
for pdu in naspdu:
if not isinstance(pdu, bytes_types):
pass
elif not self.connected.is_set():
# poor UE got disconnected, just ask it to reconnect
if not self._net_init_con():
del self.RX_HOOK
return False
elif not self._send_to_rnc_nasdt(pdu, sapi=sapi):
if isinstance(naspdu, bytes_types):
RanapProc = self.init_ranap_proc(RanapDirectTransferCN,
NAS_PDU=naspdu,
SAPI=sapi)
if RanapProc:
if not self._send_to_rnc_ranap([RanapProc]):
del self.RX_HOOK
return False
else:
self._log('INF', 'send_nas_raw: 0x%s' % hexlify(naspdu).decode('ascii'))
sleep(wait_t)
else:
del self.RX_HOOK
return False
#
elif isinstance(naspdu, (tuple, list)):
for pdu in naspdu:
ret = self.send_nas_raw(pdu, sapi, rx_hook, wait_t=1)
if not ret:
try:
del self.RX_HOOK
except:
pass
return False
#
del self.RX_HOOK
return True

View File

@ -31,6 +31,8 @@ from .utils import *
from .ProcCNRanap import *
from .ProcCNMM import *
from .HdlrUEIu import UEIuSigStack
from .HdlrUESMS import UESMSd
#------------------------------------------------------------------------------#
# UE-related Iu interface handler for the CS domain
@ -39,7 +41,7 @@ from .HdlrUEIu import UEIuSigStack
class UEMMd(SigStack):
"""UE MM handler within a UEIuCSd instance
responsible for Mobility Management signaling procedures
responsible for Mobility Management signalling procedures
"""
TRACK_PROC = True
@ -167,14 +169,15 @@ class UEMMd(SigStack):
def process(self, NasRx):
"""process a NAS MM message (NasRx) sent by the UE,
and return a NAS message (NasTx) response or None
and return a list (potentially empty) of RANAP procedure(s) to be sent
back to the RNC
"""
if self.RX_HOOK is not None:
return self.RX_HOOK(NasRx)
#
ProtDisc, Type = NasRx['ProtDisc'](), NasRx['Type']()
name = NasRx._name
# 1) check if it is a Detach Indication
if (ProtDisc, Type) == (5, 1):
if name == 'MMIMSIDetachIndication':
Proc = MMIMSIDetach(self)
self.Proc.append( Proc )
if self.TRACK_PROC:
@ -183,18 +186,10 @@ class UEMMd(SigStack):
# for the CS domain
return Proc.process(NasRx)
#
# 2) check if it is a Paging response
elif (ProtDisc, Type) == (6, 39):
Proc = RRPagingResponse(self)
self.Proc.append( Proc )
if self.TRACK_PROC:
self._proc.append(Proc)
return Proc.process(NasRx)
#
# 3) check if there is any ongoing MM procedure
# 2) check if there is any ongoing MM procedure
elif self.Proc:
# 2.1) in case of STATUS, disable all ongoing procedures
if Type == 49:
# 2.1) in case of STATUS, disable ongoing procedure(s)
if name == 'MMStatus':
self._log('WNG', 'STATUS received with %r' % NasRx['Cause'])
if self.STAT_CLEAR == 1:
#self._log('WNG', 'STATUS, disabling %r' % self.Proc[-1])
@ -202,30 +197,31 @@ class UEMMd(SigStack):
elif self.STAT_CLEAR == 2:
#self._log('WNG', 'STATUS, disabling %r' % self.Proc)
self.clear()
return None
return []
#
# 2.2) in case of expected response
elif (ProtDisc, Type) in self.Proc[-1].Filter:
elif name in self.Proc[-1].FilterStr:
Proc = self.Proc[-1]
NasTx = Proc.process(NasRx)
while self.Proc and NasTx is None and self.Iu.RanapTx is None:
RanapTxProc = Proc.process(NasRx)
while self.Proc and not RanapTxProc:
# while the top-level NAS procedure has nothing to respond and terminates,
# we postprocess() lower-level NAS procedure(s) until we have something
# to send, or the stack is empty
ProcLower = self.Proc[-1]
NasTx = ProcLower.postprocess(Proc)
RanapTxProc = ProcLower.postprocess(Proc)
Proc = ProcLower
return NasTx
return RanapTxProc
#
# 2.3) in case of unexpected NasRx
else:
self._log('WNG', 'unexpected MM message %r, sending STATUS' % NasRx['Type'])
self._log('WNG', 'unexpected %s message, sending STATUS 98' % name)
# cause 98: Message type not compatible with the protocol state
return TS24008_MM.MMStatus(Cause=98)
return self.Iu.ret_ranap_dt(NAS.MMStatus(Cause=98))
#
# 4) start a new UE-initiated procedure
elif Type in MMProcUeDispatcher:
Proc = MMProcUeDispatcher[Type](self)
# 3) start a new UE-initiated procedure
elif name in MMProcUeDispatcherStr:
# the dispatcher include the support of the RRPagingResponse message
Proc = MMProcUeDispatcherStr[name](self)
self.Proc.append( Proc )
if self.TRACK_PROC:
self._proc.append(Proc)
@ -233,9 +229,9 @@ class UEMMd(SigStack):
#
# 4) unexpected NasRx
else:
self._log('WNG', 'unexpected MM message %r, sending STATUS' % NasRx['Type'])
# cause 101: Message not compatible with the protocol state
return TS24008_MM.MMStatus(Cause=101)
self._log('WNG', 'unexpected %s message, sending STATUS 96' % name)
# cause 96: Invalid mandatory information
return self.Iu.ret_ranap_dt(NAS.MMStatus(Cause=96))
def init_proc(self, ProcClass, encod=None, mm_preempt=False):
"""initialize a CN-initiated MM procedure of class `ProcClass' and
@ -284,12 +280,12 @@ class UEMMd(SigStack):
Proc = self.init_proc(MMIdentification, mm_preempt=True)
Proc.set_msg(5, 24, IDType=idtype)
try:
NasTx = Proc.output().to_bytes()
RanapTxProc = Proc.output().to_bytes()
except:
self._log('ERR', 'NAS message encoding failed')
Proc.abort()
return False
if not self.Iu._send_to_ue(NasTx):
if not self.Iu._send_to_rnc_ranap(RanapTxProc):
# something bad happened while sending the message
return False
#
@ -317,17 +313,17 @@ class UEMMd(SigStack):
Proc = self.init_proc(MMInformation)
Proc.set_msg(5, 50, **info)
try:
NasTx = Proc.output().to_bytes()
RanapTxProc = Proc.output()
except:
self._log('ERR', 'NAS message encoding failed')
Proc.abort()
return False
return self.Iu._send_to_ue(NasTx)
return self.Iu._send_to_rnc_ranap(RanapTxProc)
class UECCd(SigStack):
"""UE CC handler within a UEIuCSd instance
responsible for Call Control signaling procedures
responsible for Call Control signalling procedures
"""
TRACK_PROC = True
@ -357,13 +353,14 @@ class UECCd(SigStack):
def process(self, NasRx):
"""process a NAS CC message (NasRx) sent by the UE,
and return a NAS message (NasTx) response or None
and return a list (potentially empty) of RANAP procedure(s) to be sent
back to the RNC
"""
if self.RX_HOOK is not None:
return self.RX_HOOK(NasRx)
#
# returns CC STATUS, cause network failure
return Buf('CC_STATUS', val=b'\x03\x61\0', bl=24)
return self.Iu.ret_ranap_dt(Buf('CCStatus', val=b'\x03\x61\0', bl=24))
def init_proc(self, ProcClass, encod=None):
"""initialize a CN-initiated CC procedure of class `ProcClass' and
@ -377,61 +374,9 @@ class UECCd(SigStack):
pass
class UESMSd(SigStack):
"""UE SMS handler within a UEIuCSd instance
responsible for poit-to-point Short Message Service procedures
"""
TRACK_PROC = True
# reference to the UEd
UE = None
# reference to the IuCSd
Iu = None
# to bypass the process() server loop with a custom NAS PDU handler
RX_HOOK = None
def _log(self, logtype, msg):
self.Iu._log(logtype, '[SMS] %s' % msg)
def __init__(self, ued, iucsd):
self.UE = ued
self.set_iu(iucsd)
#
# dict of ongoing SMS procedures (indexed by transaction identifier)
self.Proc = {}
# list of tracked procedures (requires TRACK_PROC = True)
self._proc = []
def set_iu(self, iucsd):
self.Iu = iucsd
def process(self, NasRx):
"""process a NAS SMS message (NasRx) sent by the UE,
and return a NAS message (NasTx) response or None
"""
if self.RX_HOOK is not None:
return self.RX_HOOK(NasRx)
#
# returns SMS CP ERROR, cause network failure
return Buf('SMS_CP_ERR', val=b'\x09\x0F\x11', bl=24)
def init_proc(self, ProcClass, encod=None):
"""initialize a CN-initiated SMS procedure of class `ProcClass' and
given encoder(s), and return the procedure
"""
assert()
def clear(self):
"""abort all running procedures
"""
pass
class UESSd(SigStack):
"""UE SS handler within a UEIuCSd instance
responsible for Supplementary Service procedures
responsible for Supplementary Service signalling procedures
"""
TRACK_PROC = True
@ -460,14 +405,15 @@ class UESSd(SigStack):
self.Iu = iucsd
def process(self, NasRx):
"""process a NAS SMS message (NasRx) sent by the UE,
and return a NAS message (NasTx) response or None
"""process a NAS SS message (NasRx) sent by the UE,
and return a list (potentially empty) of RANAP procedure(s) to be sent
back to the RNC
"""
if self.RX_HOOK is not None:
return self.RX_HOOK(NasRx)
#
# returns SSReleaseComplete, cause network failure
return Buf('SSReleaseComplete', val=b'\x0B\x2A\x11', bl=24)
return self.Iu.ret_ranap_dt(Buf('SSReleaseComplete', val=b'\x0B\x2A\x11', bl=24))
def init_proc(self, ProcClass, encod=None):
"""initialize a CN-initiated SS procedure of class `ProcClass' and
@ -483,7 +429,7 @@ class UESSd(SigStack):
class UEIuCSd(UEIuSigStack):
"""UE IuCS handler within a CorenetServer instance
responsible for UE-related RANAP signaling
responsible for UE-related RANAP signalling
"""
# to keep track of all CS domain RANAP / NAS procedures
@ -509,21 +455,24 @@ class UEIuCSd(UEIuSigStack):
# when self.SEC['CKSN'] is not None, the context is enabled at the RNC, e.g.
# self.SEC = {'CKSN': 0,
# 0: {'CK': b'...', 'IK': b'...', 'UEA': 1, 'UIA': 0, 'CTX': 3},
# ...}
# ...,
# 'POL': {'LUR': 0, 'CON': 0, 'PAG': 0}}
#
# a single security context contains:
# CK, IK: 16 bytes buffer, keys to be sent to the RNC during the smc procedure
# UEA, UIA: algo index, indicated by the RNC at the end of a successful smc procedure
# CTX: context of the authentication,
# 2 means 2G auth converted to 3G context
# 2 means 2G auth converted to 3G context, in this case, Kc is also available
# in the security context
# 3 means 3G auth and native context
# The POL dict indicates the authentication policy for each procedure
#--------------------------------------------------------------------------#
# RANAPSecurityModeControl policy
#--------------------------------------------------------------------------#
# this will systematically bypass all smc procedures during UE signaling
SMC_DISABLED = False
# this will bypass the smc procedure into specific UE signaling procedure
# this will bypass the smc procedure into specific UE signalling procedure
# set proc abbreviation in the list: 'LU', 'CON', 'PAG'
SMC_DISABLED_PROC = []
#
@ -568,65 +517,42 @@ class UEIuCSd(UEIuSigStack):
self.SEC['CKSN'] = None
self.SEC['POL'] = {'LUR': 0, 'CON': 0, 'PAG': 0}
def _ret_ranap_proc(self, NasTx):
if NasTx is not None:
if self.UE.TRACE_NAS_CS:
self._log('TRACE_NAS_CS_DL', '\n' + NasTx.show())
if self.RanapTx is None:
# no specific RANAP Procedure to be used
# wrap the NAS PDU into a RANAPDirectTransfer
return self.ret_ranap_dt(NasTx)
else:
# some specific RANAP Procedure(s) have already been prepared by
# the NAS stack and will embed the NAS PDU in a specific way
RanapTx = self.RanapTx
self.RanapTx = None
return RanapTx
elif self.RanapTx:
# some specific RANAP Procedure(s) have been prepared by the NAS stack
# without embedding NAS_PDU
RanapTx = self.RanapTx
self.RanapTx = None
return RanapTx
else:
# nothing to return
return []
def process_nas(self, buf):
"""process a NAS message buffer for the CS domain sent by the mobile
and return a list of RANAP procedure(s) ready to be sent back
and return a list (possibly empty) of RANAP procedure(s) to be sent back
to the RNC
"""
if self.RX_HOOK:
return self.RX_HOOK(buf)
NasRx, err = NAS.parse_NAS_MO(buf)
if NasRx is None:
if err:
self._log('WNG', 'invalid CS NAS message: %s' % hexlify(buf).decode('ascii'))
# returns MM STATUS
NasTx = TS24008_MM.MMStatus(Cause=err)
if self.UE.TRACE_NAS_CS:
self._log('TRACE_NAS_CS_DL', '\n' + NasTx.show())
return self.ret_ranap_dt(NasTx)
return self.ret_ranap_dt(NAS.MMStatus(Cause=err))
elif self.UE.TRACE_NAS_CS:
self._log('TRACE_NAS_CS_UL', '\n' + NasRx.show())
#
pd = NasRx['ProtDisc']()
pd = NasRx['ProtDisc'].get_val()
if pd in (5, 6):
# including Radio Resource Management (e.g. PAGING RESPONSE)
NasTx = self.MM.process(NasRx)
RanapTxProc = self.MM.process(NasRx)
elif pd == 3:
NasTx = self.CC.process(NasRx)
RanapTxProc = self.CC.process(NasRx)
elif pd == 9:
NasTx = self.SMS.process(NasRx)
SMSTx = self.SMS.process(NasRx)
RanapTxProc = []
for smscp in SMSTx:
RanapTxProc.extend( self.ret_ranap_dt(smscp, sapi=3) )
elif pd == 11:
NasTx = self.SS.process(NasRx)
RanapTxProc = self.SS.process(NasRx)
else:
# invalid PD
self._log('WNG', 'invalid Protocol Discriminator for CS NAS message, %i' % pd)
# returns MM STATUS, with cause message-type non-existent
# or not implemented
NasTx = TS24008_MM.MMStatus(Cause=97)
RanapTxProc = self.ret_ranap_dt(NAS.MMStatus(Cause=97))
#
return self._ret_ranap_proc(NasTx)
return RanapTxProc
def clear_nas_proc(self):
# clears all NAS CS procedures
@ -687,7 +613,7 @@ class UEIuCSd(UEIuSigStack):
'PermanentNAS_UE_ID' : ('iMSI', NAS.encode_bcd(self.UE.IMSI))}
# DRX paging cycle
if 'DRXParam' in self.UE.Cap:
drx = self.UE.Cap['DRXParam']['DRXCycleLen']()
drx = self.UE.Cap['DRXParam'][1]['DRXCycleLen'].get_val()
if drx in (6, 7, 8, 9):
IEs['DRX_CycleLengthCoefficient'] = drx
# paging with IMSI instead of TMSI
@ -767,3 +693,4 @@ class UEIuCSd(UEIuSigStack):
# defined in UEIuSigStack in HdlrUEIu.py
def _net_init_con(self):
return self.MM._net_init_con()

View File

@ -170,6 +170,13 @@ class UEGMMd(SigStack):
ATT_ADDNETFEAT_SUPP = _ADDNETFEAT_SUPP
ATT_EXTDRX = _EXTDRX
#
# if 0, enable IMSI attach from PS; if > 0, use it as error code
# e.g. 16: MSC temporarily not reachable
ATT_IMSI = 0
# if 0, enable emergency attach; if > 0, use it as error code
# e.g. 8: GPRS services and non-GPRS services not allowed
ATT_EMERG = 0
#
# if we want to run a PTMSI Reallocation within the GPRS Attach Accept
ATT_PTMSI_REALLOC = True
# radio priority for TOM8 / SMS (uint3, 1 -highest- to 4 -slowest-)
@ -182,7 +189,7 @@ class UEGMMd(SigStack):
# when a UEd with PTMSI was created, that in fact corresponds to a UE
# already set in Server.UE, we need to reject it after updating Server.PTMSI
ATT_IMSI_PROV_REJECT = 17
# Unit: 0: 2s, 1: 1mn, 2: 6mn, 7: deactivated
# timer within AttachReject, Unit: 0: 2s, 1: 1mn, 2: 6mn, 7: deactivated
ATT_T3346 = {'Unit': 0, 'Value': 2}
#--------------------------------------------------------------------------#
@ -239,27 +246,27 @@ class UEGMMd(SigStack):
def process(self, NasRx):
"""process a NAS GMM message (NasRx) sent by the UE,
and return a NAS message (NasTx) response or None
and return a list (potentially empty) of RANAP procedure(s) to be sent
back to the RNC
"""
if self.RX_HOOK is not None:
return self.RX_HOOK(NasRx)
#
ProtDisc, Type = NasRx['ProtDisc'](), NasRx['Type']()
name = NasRx._name
# 1) check if it is a Detach request
if Type == 5:
if name == 'GMMDetachRequestMO':
Proc = GMMDetachUE(self)
self.Proc.append( Proc )
if self.TRACK_PROC:
self._proc.append(Proc)
# GMMDetachUE.process() will abort every other ongoing NAS procedures
# for the CS domain
NasTx = Proc.process(NasRx)
return NasTx
# for the PS domain
return Proc.process(NasRx)
#
# 2) check if there is any ongoing GMM procedure
elif self.Proc:
# 2.1) in case of STATUS, disable all ongoing procedures
if Type == 32:
# 2.1) in case of STATUS, disable ongoing procedure(s)
if name == 'GMMStatus':
self._log('WNG', 'STATUS received with %r' % NasRx['GMMCause'])
if self.STAT_CLEAR == 1:
#self._log('WNG', 'STATUS, disabling %r' % self.Proc[-1])
@ -267,30 +274,30 @@ class UEGMMd(SigStack):
elif self.STAT_CLEAR == 2:
#self._log('WNG', 'STATUS, disabling %r' % self.Proc)
self.clear()
return None
return []
#
# 2.2) in case of expected response
elif (ProtDisc, Type) in self.Proc[-1].Filter:
elif name in self.Proc[-1].FilterStr:
Proc = self.Proc[-1]
NasTx = Proc.process(NasRx)
while self.Proc and NasTx is None and self.Iu.RanapTx is None:
RanapTxProc = Proc.process(NasRx)
while self.Proc and not RanapTxProc:
# while the top-level NAS procedure has nothing to respond and terminates,
# we postprocess() lower-level NAS procedure(s) until we have something
# to send, or the stack is empty
ProcLower = self.Proc[-1]
NasTx = ProcLower.postprocess(Proc)
RanapTxProc = ProcLower.postprocess(Proc)
Proc = ProcLower
return NasTx
return RanapTxProc
#
# 2.3) in case of unexpected NasRx
else:
self._log('WNG', 'unexpected GMM message %r, sending STATUS' % NasRx['Type'])
self._log('WNG', 'unexpected %s message, sending STATUS 98' % name)
# cause 98: Message type not compatible with the protocol state
return TS24008_GMM.GMMStatus(GMMCause=98)
return self.Iu.ret_ranap_dt(NAS.GMMStatus(GMMCause=98))
#
# 3) start a new UE-initiated procedure
elif Type in GMMProcUeDispatcher:
Proc = GMMProcUeDispatcher[Type](self)
elif name in GMMProcUeDispatcherStr:
Proc = GMMProcUeDispatcherStr[name](self)
self.Proc.append( Proc )
if self.TRACK_PROC:
self._proc.append(Proc)
@ -298,9 +305,9 @@ class UEGMMd(SigStack):
#
# 4) unexpected NasRx
else:
self._log('WNG', 'unexpected GMM message %r, sending STATUS' % NasRx['Type'])
# cause 101: Message not compatible with the protocol state
return TS24008_GMM.GMMStatus(Cause=101)
self._log('WNG', 'unexpected %s message, sending STATUS 96' % name)
# cause 96: Invalid mandatory information
return self.Iu.ret_ranap_dt(NAS.GMMStatus(Cause=96))
def init_proc(self, ProcClass, encod=None, gmm_preempt=False):
"""initialize a CN-initiated GMM procedure of class `ProcClass' and
@ -349,12 +356,12 @@ class UEGMMd(SigStack):
Proc = self.init_proc(GMMIdentification, gmm_preempt=True)
Proc.set_msg(8, 21, IDType=idtype)
try:
NasTx = Proc.output().to_bytes()
RanapTxProc = Proc.output()
except:
self._log('ERR', 'NAS message encoding failed')
Proc.abort()
return False
if not self.Iu._send_to_ue(NasTx):
if not self.Iu._send_to_rnc_ranap(RanapTxProc):
# something bad happened while sending the message
return False
#
@ -392,12 +399,12 @@ class UEGMMd(SigStack):
else:
Proc.set_msg(8, 5, DetachTypeMT={'Type': type})
try:
NasTx = Proc.output().to_bytes()
RanapTxProc = Proc.output()
except:
self._log('ERR', 'NAS message encoding failed')
Proc.abort()
return False
if not self.Iu._send_to_ue(NasTx):
if not self.Iu._send_to_rnc_ranap(RanapTxProc):
# something bad happened while sending the message
return False
#
@ -423,12 +430,12 @@ class UEGMMd(SigStack):
Proc = self.init_proc(GMMInformation)
Proc.set_msg(8, 33, **info)
try:
NasTx = Proc.output().to_bytes()
RanapTxProc = Proc.output()
except:
self._log('ERR', 'NAS message encoding failed')
Proc.abort()
return False
return self.Iu._send_to_ue(NasTx)
return self.Iu._send_to_rnc_ranap(RanapTxProc)
class UESMd(SigStack):
@ -446,6 +453,15 @@ class UESMd(SigStack):
# to bypass the process() server loop with a custom NAS PDU handler
RX_HOOK = None
# Packet Data Protocol configuration, per APN
# PDP default IuPS QoS (TODO)
PDPConfig = {
'*' : {},
'corenet': {}
}
# PDP UE config, per APN, built at runtime
PDP = {}
def _log(self, logtype, msg):
self.Iu._log(logtype, '[SM] %s' % msg)
@ -453,8 +469,10 @@ class UESMd(SigStack):
self.UE = ued
self.set_iu(iupsd)
#
# dict of ongoing SM procedures (indexed by transaction identifier)
# dict of ongoing SM procedures (indexed by NSAPI)
self.Proc = {}
# dict of ongoing ESM transactions IEs
self.Trans = {}
# list of tracked procedures (requires TRACK_PROC = True)
self._proc = []
@ -463,15 +481,16 @@ class UESMd(SigStack):
def process(self, NasRx):
"""process a NAS SM message (NasRx) sent by the UE,
and return a NAS message (NasTx) response or None
and return a list (potentially empty) of RANAP procedure(s) to be sent
back to the RNC
"""
if self.RX_HOOK is not None:
return self.RX_HOOK(NasRx)
#
# returns SM STATUS, cause feature not supported
return Buf('SM_STATUS', val=b'\x0A\x55\x28', bl=24)
return self.Iu.ret_ranap_dt(Buf('SMStatus', val=b'\x0A\x55\x28', bl=24))
def init_proc(self, Proc, **kw):
def init_proc(self, Proc, encod=None):
"""initialize a CN-initiated SM procedure of class `ProcClass' and
given encoder(s), and return the procedure
"""
@ -511,21 +530,24 @@ class UEIuPSd(UEIuSigStack):
# when self.SEC['CKSN'] is not None, the context is enabled at the RNC, e.g.
# self.SEC = {'CKSN': 0,
# 0: {'CK': b'...', 'IK': b'...', 'UEA': 1, 'UIA': 0, 'CTX': 3},
# ...}
# ...,
# 'POL': {'RAU': 0, 'SER': 0}}
#
# a single security context contains:
# CK, IK: 16 bytes buffer, keys to be sent to the RNC during the smc procedure
# UEA, UIA: algo index, indicated by the RNC at the end of a successful smc procedure
# CTX: context of the authentication,
# 2 means 2G auth converted to 3G context
# 2 means 2G auth converted to 3G context, in this case, Kc is also available
# in the security context
# 3 means 3G auth and native context
# The POL dict indicates the authentication policy for each procedure
#--------------------------------------------------------------------------#
# RANAPSecurityModeControl policy
#--------------------------------------------------------------------------#
# this will systematically bypass all smc procedures during UE signaling
SMC_DISABLED = False
# this will bypass the smc procedure into specific UE signaling procedure
# this will bypass the smc procedure into specific UE signalling procedure
# set proc abbreviation in the list: 'ATT', 'RAU', 'SER'
SMC_DISABLED_PROC = []
#
@ -564,70 +586,44 @@ class UEIuPSd(UEIuSigStack):
self.Config = self.Server.ConfigIuPS
# track states for PDP and MBMS contexts
self.PDP = {i: 0 for i in range(16)}
self.MBMS = {i: 0 for i in range(16)}
self.MBMS = {i: 0 for i in range(16)} # to be confirmed if only 16 ctx
def reset_sec_ctx(self):
self.SEC.clear()
self.SEC['CKSN'] = None
self.SEC['POL'] = {'RAU': 0, 'SER': 0}
def _ret_ranap_proc(self, NasTx):
if NasTx is not None:
if self.UE.TRACE_NAS_PS:
self._log('TRACE_NAS_PS_DL', '\n' + NasTx.show())
if self.RanapTx is None:
# no specific RANAP Procedure to be used
# wrap the NAS PDU into a RANAPDirectTransfer
return self.ret_ranap_dt(NasTx)
else:
# some specific RANAP Procedure(s) have already been prepared by
# the NAS stack and will embed the NAS PDU in a specific way
RanapTx = self.RanapTx
self.RanapTx = None
return RanapTx
elif self.RanapTx:
# some specific RANAP Procedure(s) have been prepared by the NAS stack
# without embedding NAS_PDU
RanapTx = self.RanapTx
self.RanapTx = None
return RanapTx
else:
# nothing to return
return []
def process_nas(self, buf):
"""process a NAS message buffer for the PS domain sent by the mobile
and return a potential NAS response to be sent back to it
and return a list (possibly empty) of RANAP procedure(s) to be sent back
to the RNC
"""
if self.RX_HOOK:
return self.RX_HOOK(buf)
NasRx, err = NAS.parse_NAS_MO(buf)
if NasRx is None:
if err:
self._log('WNG', 'invalid PS NAS message: %s' % hexlify(buf).decode('ascii'))
# returns MM STATUS
NasTx = TS24008_GMM.GMMStatus(Cause=err)
if self.UE.TRACE_NAS_PS:
self._log('TRACE_NAS_PS_DL', '\n' + NasTx.show())
return self.ret_ranap_dt(NasTx)
# returns GMM STATUS
return self.ret_ranap_dt(NAS.GMMStatus(Cause=err))
elif self.UE.TRACE_NAS_PS:
self._log('TRACE_NAS_PS_UL', '\n' + NasRx.show())
#
pd = NasRx['ProtDisc']()
pd = NasRx['ProtDisc'].get_val()
if pd == 8:
NasTx = self.GMM.process(NasRx)
RanapTxProc = self.GMM.process(NasRx)
elif pd == 6:
# Radio Resource Management (e.g. PAGING RESPONSE)
NasTx = self.GMM.process(NasRx)
RanapTxProc = self.GMM.process(NasRx)
elif pd == 10:
NasTx = self.SM.process(NasRx)
RanapTxProc = self.SM.process(NasRx)
else:
# invalid PD
self._log('WNG', 'invalid Protocol Discriminator for PS NAS message, %i' % pd)
# returns GMM STATUS, with cause message-type non-existent
# or not implemented
NasTx = TS24008_GMM.GMMStatus(GMMCause=97)
RanapTxProc = self.ret_ranap_dt(NAS.GMMStatus(GMMCause=97))
#
return self._ret_ranap_proc(NasTx)
return RanapTxProc
def clear_nas_proc(self):
# clears all NAS PS procedures
@ -680,7 +676,7 @@ class UEIuPSd(UEIuSigStack):
'PermanentNAS_UE_ID' : ('iMSI', NAS.encode_bcd(self.UE.IMSI))}
# DRX paging cycle
if 'DRXParam' in self.UE.Cap:
drx = self.UE.Cap['DRXParam']['DRXCycleLen']()
drx = self.UE.Cap['DRXParam'][1]['DRXCycleLen'].get_val()
if drx in (6, 7, 8, 9):
IEs['DRX_CycleLengthCoefficient'] = drx
# paging with IMSI instead of P-TMSI
@ -758,3 +754,4 @@ class UEIuPSd(UEIuSigStack):
# defined in UEIuSigStack in HdlrUEIu.py
def _net_init_con(self):
return self.GMM._net_init_con()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,84 @@
# -*- coding: UTF-8 -*-
#/**
# * Software Name : pycrate
# * Version : 0.2
# *
# * Copyright 2017. Benoit Michau. ANSSI.
# *
# * This program is free software; you can redistribute it and/or
# * modify it under the terms of the GNU General Public License
# * as published by the Free Software Foundation; either version 2
# * 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 General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# * 02110-1301, USA.
# *
# *--------------------------------------------------------
# * File Name : pycrate_corenet/HdlrUESMS.py
# * Created : 2017-12-04
# * Authors : Benoit Michau
# *--------------------------------------------------------
#*/
from .utils import *
class UESMSd(SigStack):
"""UE SMS handler within a UEIuCSd or UES1d instance
responsible for point-to-point Short Message Service procedures
"""
TRACK_PROC = True
# reference to the UEd
UE = None
# reference to the UEIuCSd / UES1d
RAN = None
# to bypass the process() server loop with a custom NAS PDU handler
RX_HOOK = None
def _log(self, logtype, msg):
self.RAN._log(logtype, '[SMS] %s' % msg)
def __init__(self, ued, rand):
self.UE = ued
self.set_ran(rand)
#
# dict of ongoing SMS procedures (indexed by transaction identifier)
self.Proc = {}
# list of tracked procedures (requires TRACK_PROC = True)
self._proc = []
def set_ran(self, rand):
self.RAN = rand
def process(self, SMSRx):
"""process a SMS-CP message (SMSRx) sent by the UE,
and return a list (potentially empty) of SMS-CP messages back to the UE
"""
if self.RX_HOOK is not None:
return self.RX_HOOK(NasRx)
#
# returns SMS CP ERROR, cause network failure
return [Buf('SMS_CP_ERR', val=b'\x09\x0F\x11', bl=24)]
def init_proc(self, ProcClass, encod=None):
"""initialize a CN-initiated SMS procedure of class `ProcClass' and
given encoder(s), and return the procedure
"""
assert()
def clear(self):
"""abort all running procedures
"""
pass

1359
pycrate_corenet/ProcCNEMM.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,693 @@
# -*- coding: UTF-8 -*-
#/**
# * Software Name : pycrate
# * Version : 0.2
# *
# * Copyright 2017. Benoit Michau. ANSSI.
# *
# * This program is free software; you can redistribute it and/or
# * modify it under the terms of the GNU General Public License
# * as published by the Free Software Foundation; either version 2
# * 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 General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# * 02110-1301, USA.
# *
# *--------------------------------------------------------
# * File Name : pycrate_corenet/ProcCNESM.py
# * Created : 2017-12-15
# * Authors : Benoit Michau
# *--------------------------------------------------------
#*/
from .utils import *
from .ProcProto import *
from .ProcCNS1ap import *
TESTING = False
#------------------------------------------------------------------------------#
# NAS EPS Session Management signalling procedure
# TS 24.301, version da0
# Core Network side
#------------------------------------------------------------------------------#
class ESMSigProc(NASSigProc):
"""EPS Session Management signalling procedure handler
instance attributes:
- Name : procedure name
- ESM : reference to the UEESMd instance running this procedure
- S1 : reference to the UES1d instance connecting the UE
- Cont : 2-tuple of CN-initiated NAS message(s) and UE-initiated NAS
message(s)
- Timer: timer in sec. for this procedure
- Encod: custom NAS message encoders with fixed values
- Decod: custom NAS message decoders with transform functions
"""
# tacking all exchanged NAS message within the procedure
TRACK_PDU = True
# potential timer
Timer = None
TimerDefault = 4
if TESTING:
def __init__(self, encod=None):
self._prepare(encod)
self._log('DBG', 'instantiating procedure')
def _log(self, logtype, msg):
log('[TESTING] [%s] [EMMSigProc] [%s] %s' % (logtype, self.Name, msg))
else:
def __init__(self, esmd, encod=None, sec=True, ebi=0, EMMProc=None):
self._prepare(encod)
self.ESM = esmd
self.S1 = esmd.S1
self.UE = esmd.UE
self._sec = sec
self._ebi = ebi
self._EMMProc = EMMProc
self._log('DBG', 'instantiating procedure')
def _log(self, logtype, msg):
self.ESM._log(logtype, '[%s [%i]] %s' % (self.Name, self._ebi, msg))
def decode_msg(self, msg, ret):
NASSigProc.decode_msg(self, msg, ret)
# add EPSBearerId and PTI into ret
ret['EPSBearerId'] = msg[0].get_val()
ret['PTI'] = msg[2].get_val()
def output(self):
self._log('ERR', 'output() not implemented')
return None
def process(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
#
self._log('ERR', 'process() not implemented')
return None
def postprocess(self, Proc=None):
self._log('ERR', 'postprocess() not implemented')
self.rm_from_esm_stack()
return None
def abort(self):
# abort this procedure, and all procedures started within this one
ProcStack = self.ESM.Proc[self._ebi]
ind = ProcStack.index(self)
if ind >= 0:
for p in ProcStack[ind+1:]:
p.abort()
del ProcStack[ind:]
self._log('INF', 'aborting')
def rm_from_esm_stack(self):
# remove the procedure from the EMM stack of procedures
ProcStack = self.ESM.Proc[self._ebi]
if ProcStack[-1] == self:
del ProcStack[-1]
def init_timer(self):
if self.Timer is not None:
self.TimerValue = getattr(self.ESM, self.Timer, self.TimerDefault)
self.TimerStart = time()
self.TimerStop = self.TimerStart + self.TimerValue
def get_timer(self):
if self.Timer is None:
return None
else:
return getattr(self.ESM, self.Timer)
#--------------------------------------------------------------------------#
# common helpers
#--------------------------------------------------------------------------#
def _collect_cap(self):
if not hasattr(self, 'Cap') or not hasattr(self, 'UEInfo'):
return
for Cap in self.Cap:
if Cap in self.UEInfo:
self.UE.Cap[Cap] = self.UEInfo[Cap]
def _init_trans(self, UEInfo, Type):
trans = {'Type' : Type,
'EPSBearerId': UEInfo['EPSBearerId']}
if Type == 'Default':
trans.update({
'PDNType' : UEInfo['PDNType'],
'RequestType': UEInfo['RequestType'],
'APN' : UEInfo.get('APN', None),
'ProtConfig' : UEInfo.get('ProtConfig', None),
#'DeviceProp' : None,
#'NBIFOMContainer': None,
#'HdrCompConfig' : None,
#'ExtProtConfig' : None,
})
elif Type == 'Dedicated':
pass
elif Type == 'Modif':
pass
elif Type == 'Deact':
pass
self.ESM.Trans[UEInfo['PTI']] = trans
#------------------------------------------------------------------------------#
# Network-initiated ESM procedures: TS 24.301, section 6.4
#------------------------------------------------------------------------------#
class ESMDefaultEPSBearerCtxtAct(ESMSigProc):
"""Default EPS bearer context activation procedure: TS 24.301, section 6.4.1
CN-initiated
CN message:
ESMActDefaultEPSBearerCtxtRequest (PD 2, Type 193), IEs:
- Type4LV : EPSQoS
- Type4LV : APN
- Type4LV : PDNAddr
- Type4TLV : TransId
- Type4TLV : QoS
- Type3TV : LLC_SAPI
- Type1TV : RadioPriority
- Type4TLV : PacketFlowId
- Type4TLV : APN_AMBR
- Type3TV : ESMCause
- Type4TLV : ProtConfig
- Type1TV : ConType
- Type1TV : WLANOffloadInd
- Type4TLV : NBIFOMContainer
- Type4TLV : HdrCompConfig
- Type1TV : CPOnlyInd
- Type6TLVE : ExtProtConfig
- Type4TLV : ServingPLMNRateCtrl
UE messages:
ESMActDefaultEPSBearerCtxtAccept (PD 2, Type 194), IEs:
- Type4TLV : ProtConfig
- Type6TLVE : ExtProtConfig
ESMActDefaultEPSBearerCtxtReject (PD 2, Type 195), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMActDefaultEPSBearerCtxtRequest, ),
(TS24301_ESM.ESMActDefaultEPSBearerCtxtAccept, TS24301_ESM.ESMActDefaultEPSBearerCtxtReject)
)
Timer = 'T3485'
class ESMDedicatedEPSBearerCtxtAct(ESMSigProc):
"""Dedicated EPS bearer context activation procedure: TS 24.301, section 6.4.2
CN-initiated
CN message:
ESMActDediEPSBearerCtxtRequest (PD 2, Type 197), IEs:
- Type1V : spare
- Type1V : LinkedEPSBearerId
- Type4LV : EPSQoS
- Type4LV : TFT
- Type4TLV : TransId
- Type4TLV : QoS
- Type3TV : LLC_SAPI
- Type1TV : RadioPriority
- Type4TLV : PacketFlowId
- Type4TLV : ProtConfig
- Type1TV : WLANOffloadInd
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
UE messages:
ESMActDediEPSBearerCtxtAccept (PD 2, Type 198), IEs:
- Type4TLV : ProtConfig
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
ESMActDediEPSBearerCtxtReject (PD 2, Type 199), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMActDediEPSBearerCtxtRequest, ),
(TS24301_ESM.ESMActDediEPSBearerCtxtAccept, TS24301_ESM.ESMActDediEPSBearerCtxtReject)
)
Timer = 'T3485'
class ESMEPSBearerCtxtModif(ESMSigProc):
"""EPS bearer context modification procedure: TS 24.301, section 6.4.3
CN-initiated
CN message:
ESMModifyEPSBearerCtxtRequest (PD 2, Type 201), IEs:
- Type4TLV : EPSQoS
- Type4TLV : TFT
- Type4TLV : QoS
- Type3TV : LLC_SAPI
- Type1TV : RadioPriority
- Type4TLV : PacketFlowId
- Type4TLV : APN_AMBR
- Type4TLV : ProtConfig
- Type1TV : WLANOffloadInd
- Type4TLV : NBIFOMContainer
- Type4TLV : HdrCompConfig
- Type6TLVE : ExtProtConfig
UE messages:
ESMModifyEPSBearerCtxtAccept (PD 2, Type 202), IEs:
- Type4TLV : ProtConfig
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
ESMModifyEPSBearerCtxtReject (PD 2, Type 203), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMModifyEPSBearerCtxtRequest, ),
(TS24301_ESM.ESMModifyEPSBearerCtxtAccept, TS24301_ESM.ESMModifyEPSBearerCtxtReject)
)
Timer = 'T3486'
class ESMEPSBearerCtxtDeact(ESMSigProc):
"""EPS bearer context deactivation procedure: TS 24.301, section 6.4.4
CN-initiated
CN message:
ESMDeactEPSBearerCtxtRequest (PD 2, Type 205), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type4TLV : BackOffTimer
- Type1TV : WLANOffloadInd
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
UE message:
ESMDeactEPSBearerCtxtAccept (PD 2, Type 206), IEs:
- Type4TLV : ProtConfig
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMDeactEPSBearerCtxtRequest, ),
(TS24301_ESM.ESMDeactEPSBearerCtxtAccept, )
)
Timer = 'T3495'
#------------------------------------------------------------------------------#
# UE requested ESM procedures: TS 24.301, section 6.5
#------------------------------------------------------------------------------#
class ESMPDNConnectivityRequest(ESMSigProc):
"""UE requested PDN connectivity procedure: TS 24.301, section 6.5.1
UE-initiated
triggers ESMDefaultEPSBearerCtxtAct
CN message:
ESMPDNConnectivityReject (PD 2, Type 209), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type4TLV : BackOffTimer
- Type4TLV : ReattemptInd
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
UE message:
ESMPDNConnectivityRequest (PD 2, Type 208), IEs:
- Type1V : PDNType
- Type1V : RequestType
- Type1TV : ESMInfoTransferFlag
- Type4TLV : APN
- Type4TLV : ProtConfig
- Type1TV : DeviceProp
- Type4TLV : NBIFOMContainer
- Type4TLV : HdrCompConfig
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMPDNConnectivityReject, ),
(TS24301_ESM.ESMPDNConnectivityRequest, )
)
Decod = {
(2, 208): {
'ESMInfoTransferFlag' : lambda x: x[1]['Value'].get_val(),
},
}
def process(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
#
# insert the info into the ESM transaction dict
self._init_trans(self.UEInfo, Type='Default')
#
if 'ESMInfoTransferFlag' in self.UEInfo and self.UEInfo['ESMInfoTransferFlag']:
# initiate an info transfer proc to get complementary info
NasProc = self.ESM.init_proc(ESMInfoRequest, ebi=self.UEInfo['EPSBearerId'])
NasProc.set_msg(2, 217, EPSBearerId=self.UEInfo['EPSBearerId'],
PTI=self.UEInfo['PTI'])
return NasProc.output()
#
else:
self.postprocess()
def postprocess(self, Proc=None):
if isinstance(Proc, ESMInfoRequest):
if not Proc.success:
self.abort()
return []
#
elif Proc != self and Proc is not None:
self._err = Proc
assert()
#
return self.output()
def output(self):
# process the whole transaction request
ret, self.errcause = self.ESM.process_trans(self.UEInfo['PTI'])
# deny request or start an ESNDefaultEPSBearerCtxtAct
if self.errcause:
self.set_msg(2, 209, ESMCause=self.errcause)
self.encode_msg(2, 209)
else:
# TODO
pass
class ESMPDNDisconnectRequest(ESMSigProc):
"""UE requested PDN disconnect procedure: TS 24.301, section 6.5.2
UE-initiated
triggers ESMEPSBearerCtxtDeact
CN message:
ESMPDNDisconnectReject (PD 2, Type 211), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type6TLVE : ExtProtConfig
UE message:
ESMPDNDisconnectRequest (PD 2, Type 210), IEs:
- Type1V : spare
- Type1V : LinkedEPSBearerId
- Type4TLV : ProtConfig
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMPDNDisconnectReject, ),
(TS24301_ESM.ESMPDNDisconnectRequest, )
)
class ESMBearerResourceAllocRequest(ESMSigProc):
"""UE requested bearer resource allocation procedure: TS 24.301, section 6.5.3
UE-initiated
triggers ESMDedicatedEPSBearerCtxtAct or ESMEPSBearerCtxtModif
CN message:
ESMBearerResourceAllocReject (PD 2, Type 213), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type4TLV : BackOffTimer
- Type4TLV : ReattemptInd
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
UE message:
ESMBearerResourceAllocRequest (PD 2, Type 212), IEs:
- Type1V : spare
- Type1V : LinkedEPSBearerId
- Type4LV : TFAggregate
- Type4LV : EPSQoS
- Type4TLV : ProtConfig
- Type1TV : DeviceProp
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMBearerResourceAllocReject, ),
(TS24301_ESM.ESMBearerResourceAllocRequest, )
)
class ESMBearerResourceModifRequest(ESMSigProc):
"""UE requested bearer resource modification procedure: TS 24.301, section 6.5.4
UE-initiated
triggers ESMDedicatedEPSBearerCtxtAct or ESMEPSBearerCtxtModif or ESMEPSBearerCtxtDeact
CN message:
ESMBearerResourceModifReject (PD 2, Type 215), IEs:
- Type2 : ESMCause
- Type4TLV : ProtConfig
- Type4TLV : BackOffTimer
- Type4TLV : ReattemptInd
- Type4TLV : NBIFOMContainer
- Type6TLVE : ExtProtConfig
UE message:
ESMBearerResourceModifRequest (PD 2, Type 214), IEs:
- Type1V : spare
- Type1V : LinkedEPSBearerId
- Type4LV : TFAggregate
- Type4TLV : EPSQoS
- Type3TV : ESMCause
- Type4TLV : ProtConfig
- Type1TV : DeviceProp
- Type4TLV : NBIFOMContainer
- Type4TLV : HdrCompConfig
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMBearerResourceModifReject, ),
(TS24301_ESM.ESMBearerResourceModifRequest, )
)
#------------------------------------------------------------------------------#
# Miscellaneous procedures: TS 24.301, section 6.6
#------------------------------------------------------------------------------#
class ESMInfoRequest(ESMSigProc):
"""ESM information request procedure: TS 24.301, section 6.6.1.2
CN-initiated
CN message:
ESMInformationRequest (PD 2, Type 217), IEs:
None
UE message:
ESMInformationResponse (PD 2, Type 218), IEs:
- Type4TLV : APN
- Type4TLV : ProtConfig
- Type6TLVE : ExtProtConfig
"""
Cont = (
(TS24301_ESM.ESMInformationRequest, ),
(TS24301_ESM.ESMInformationResponse, )
)
Timer = 'T3489'
def output(self):
self.encode_msg(2, 217)
if not self._sec:
self._nas_tx._sec = False
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
nas_tx = self.ESM.output_nas_esm(self._nas_tx, self._EMMProc)
self.init_timer()
return self.S1.ret_s1ap_dnt(nas_tx)
def process(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
#
if self.UEInfo['PTI'] not in self.ESM.Trans:
self.success = False
else:
trans = self.ESM.Trans[self.UEInfo['PTI']]
if 'APN' in self.UEInfo:
if trans['APN']:
self._log('WNG', 'overwriting APN %r with %r'\
% (trans['APN'], self.UEInfo['APN']))
trans['APN'] = self.UEInfo['APN']
if 'ProtConfig' in self.UEInfo:
if trans['ProtConfig']:
self._log('WNG', 'overwriting ProtConfig %r with %r'\
% (trans['ProtConfig'], self.UEInfo['ProtConfig']))
trans['ProtConfig'] = self.UEInfo['ProtConfig']
self.success = True
#
self.rm_from_esm_stack()
return []
class ESMNotification(ESMSigProc):
"""Notification procedure: TS 24.301, section 6.6.2
CN-initiated
CN message:
ESMNotification (PD 2, Type 219), IEs:
- Type4LV : NotificationInd
UE message:
None
"""
Cont = (
(TS24301_ESM.ESMNotification, ),
None
)
class ESMRemoteUEReport(ESMSigProc):
"""Remote UE Report procedure: TS 24.301, section 6.6.3
UE-initiated
CN message:
ESMRemoteUEResponse (PD 2, Type 234), IEs:
None
UE message:
ESMRemoteUEReport (PD 2, Type 233), IEs:
- Type6TLVE : RemoteUEConnected
- Type6TLVE : RemoteUEDisconnected
- Type4TLV : PKMFAddr
"""
Cont = (
(TS24301_ESM.ESMRemoteUEResponse, ),
(TS24301_ESM.ESMRemoteUEReport, )
)
class ESMDataTransportUE(ESMSigProc):
"""UE initiated transport of user data via the control plane: TS 24.301, section 6.6.4.2
UE-initiated
CN message:
None
UE message:
ESMDataTransport (PD 2, Type 235), IEs:
- Type6LVE : UserData
- Type1TV : ReleaseAssistInd
"""
Cont = (
None,
(TS24301_ESM.ESMDataTransport, )
)
class ESMDataTransportCN(ESMSigProc):
"""Network initiated transport of user data via the control plane: TS 24.301, section 6.6.4.3
CN-initiated
CN message:
ESMDataTransport (PD 2, Type 235), IEs:
- Type6LVE : UserData
- Type1TV : ReleaseAssistInd
UE message:
None
"""
Cont = (
(TS24301_ESM.ESMDataTransport, ),
None
)
ESMDefaultEPSBearerCtxtAct.init(filter_init=1)
ESMDedicatedEPSBearerCtxtAct.init(filter_init=1)
ESMEPSBearerCtxtModif.init(filter_init=1)
ESMEPSBearerCtxtDeact.init(filter_init=1)
ESMPDNConnectivityRequest.init(filter_init=1)
ESMPDNDisconnectRequest.init(filter_init=1)
ESMBearerResourceAllocRequest.init(filter_init=1)
ESMBearerResourceModifRequest.init(filter_init=1)
ESMInfoRequest.init(filter_init=1)
ESMNotification.init(filter_init=1)
ESMDataTransportUE.init(filter_init=1)
ESMDataTransportCN.init(filter_init=1)
# ESM UE-initiated procedures dispatcher
ESMProcUeDispatcher = {
208 : ESMPDNConnectivityRequest,
210 : ESMPDNDisconnectRequest,
212 : ESMBearerResourceAllocRequest,
214 : ESMBearerResourceModifRequest,
235 : ESMDataTransportUE,
}
ESMProcUeDispatcherStr = {ProcClass.Cont[1][0]()._name: ProcClass \
for ProcClass in ESMProcUeDispatcher.values()}
# ESM CN-initiated procedures dispatcher
ESMProcCnDispatcher = {
193 : ESMDefaultEPSBearerCtxtAct,
197 : ESMDedicatedEPSBearerCtxtAct,
201 : ESMEPSBearerCtxtModif,
205 : ESMEPSBearerCtxtDeact,
217 : ESMInfoRequest,
219 : ESMNotification,
235 : ESMDataTransportCN,
}
ESMProcCnDispatcherStr = {ProcClass.Cont[0][0]()._name: ProcClass \
for ProcClass in ESMProcCnDispatcher.values()}

View File

@ -35,20 +35,20 @@ from .ProcCNRanap import *
TESTING = False
#------------------------------------------------------------------------------#
# NAS GPRS Mobility Management signaling procedure
# NAS GPRS Mobility Management signalling procedures
# TS 24.008, version d90
# Core Network side
#------------------------------------------------------------------------------#
class GMMSigProc(NASSigProc):
"""GPRS Mobility Management signaling procedure handler
"""GPRS Mobility Management signalling procedure handler
instance attributes:
- Name : procedure name
- GMM : reference to the UEGMMd instance running this procedure
- Iu : reference to the IuPSd instance connecting the UE
- Cont : 2-tuple of CN-initiated NAS message(s) and UE-initiated NAS
message(s)
message(s)
- Timer: timer in sec. for this procedure
- Encod: custom NAS message encoders with fixed values
- Decod: custom NAS message decoders with transform functions
@ -85,7 +85,7 @@ class GMMSigProc(NASSigProc):
def output(self):
self._log('ERR', 'output() not implemented')
return None
return []
def process(self, pdu):
if self.TRACK_PDU:
@ -94,18 +94,11 @@ class GMMSigProc(NASSigProc):
self.decode_msg(pdu, self.UEInfo)
#
self._log('ERR', 'process() not implemented')
return None
return []
def postprocess(self, Proc=None):
self._log('ERR', 'postprocess() not implemented')
return None
def _collect_cap(self):
if not hasattr(self, 'Cap') or not hasattr(self, 'UEInfo'):
return
for Cap in self.Cap:
if Cap in self.UEInfo:
self.UE.Cap[Cap] = self.UEInfo[Cap]
return []
def abort(self):
# abort this procedure, and all procedures started within this one
@ -120,7 +113,7 @@ class GMMSigProc(NASSigProc):
self._log('INF', 'aborting')
def rm_from_gmm_stack(self):
# remove the procedure from the MM stack of procedures
# remove the procedure from the GMM stack of procedures
if self.GMM.Proc[-1] == self:
del self.GMM.Proc[-1]
if self._gmm_preempt:
@ -147,9 +140,15 @@ class GMMSigProc(NASSigProc):
# common helpers
#--------------------------------------------------------------------------#
def _collect_cap(self):
if not hasattr(self, 'Cap') or not hasattr(self, 'UEInfo'):
return
for Cap in self.Cap:
if Cap in self.UEInfo:
self.UE.Cap[Cap] = self.UEInfo[Cap]
def _chk_imsi(self):
# arriving here means the UE's IMSI was unknown at first
# set the IMSI indicated by the UE
Server, imsi = self.UE.Server, self.UE.IMSI
if not Server.is_imsi_allowed(imsi):
self.errcause = self.GMM.IDENT_IMSI_NOT_ALLOWED
@ -200,13 +199,15 @@ class GMMSigProc(NASSigProc):
return NasProc.output()
def _ret_smc(self, cksn=None, newkey=False):
# set a RANAP callback in the Iu stack for triggering an SMC
# initialize a RANAP SMC procedure
RanapProc = self.Iu.init_ranap_proc(RANAPSecurityModeControl,
**self.Iu.get_smc_ies(cksn, newkey))
RanapProc._cb = self
if RanapProc:
self.Iu.RanapTx = [RanapProc]
return None
# and set a callback to self in it
RanapProc._cb = self
return [RanapProc]
else:
return []
#------------------------------------------------------------------------------#
@ -290,27 +291,26 @@ class GMMAttach(GMMSigProc):
(TS24008_GMM.GMMAttachRequest, TS24008_GMM.GMMAttachComplete)
)
Decod = {
(8, 1) : {
'CKSN' : lambda x: x(),
'ID' : lambda x: x[1].decode(),
'OldRAI' : lambda x: (x['PLMN'].decode(), x['LAC'](), x['RAC']()),
'ReqREADYTimer' : lambda x: {'Unit': x[1]['Unit'](), 'Value': x[1]['Value']()},
}
}
Cap = ('MSNetCap', 'DRXParam', 'MSRACap', 'PSLCSCap', 'MSCm2', 'MSCm3',
'SuppCodecs', 'UENetCap', 'VoiceDomPref', 'DeviceProp', 'MSNetFeatSupp',
'ExtDRXParam')
Decod = {
(8, 1) : {
'CKSN' : lambda x: x.get_val(),
'ID' : lambda x: x[1].decode(),
'OldRAI' : lambda x: x.decode(),
'ReqREADYTimer' : lambda x: {'Unit': x[1]['Unit'].get_val(), 'Value': x[1]['Value'].get_val()},
}
}
def process(self, pdu):
# got a GMMAttachRequest
# preempt the GMM stack
self.gmm_preempt()
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
if pdu['Type']() == 1:
if pdu._name == 'GMMAttachRequest':
# AttachRequest
self.errcause, self.UEInfo = None, {}
self.decode_msg(pdu, self.UEInfo)
@ -319,10 +319,10 @@ class GMMAttach(GMMSigProc):
# AttachComplete
self.errcause, self.CompInfo = None, {}
self.decode_msg(pdu, self.CompInfo)
self.UE.set_ptmsi(self.ptmsi_realloc)
self._log('INF', 'new P-TMSI set, 0x%.8x' % self.ptmsi_realloc)
self._end(nas_tx=False)
return None
if self.ptmsi_realloc >= 0:
self.UE.set_ptmsi(self.ptmsi_realloc)
self._log('INF', 'new P-TMSI set, 0x%.8x' % self.ptmsi_realloc)
return self._end()
def _process_req(self):
#
@ -330,8 +330,9 @@ class GMMAttach(GMMSigProc):
self.UE.PTMSI = self.UEInfo['ID'][1]
#
att_type = self.UEInfo['AttachType']['Type']
self.att_type = att_type.get_val()
self._log('INF', 'request type %i (%s), old RAI %s.%.4x.%.2x'\
% (att_type(), att_type._dic[att_type()],
% (att_type(), att_type._dic[self.att_type],
self.UEInfo['OldRAI'][0], self.UEInfo['OldRAI'][1], self.UEInfo['OldRAI'][2]))
# collect capabilities
self._collect_cap()
@ -341,6 +342,17 @@ class GMMAttach(GMMSigProc):
else:
self._req_imeisv = False
#
# check for emergency attach
if self.att_type == 4:
if self.GMM.ATT_EMERG:
self.errcause = self.GMM.ATT_EMERG
return self.output()
else:
# jump directly to the attach accept
self.EMM.set_sec_ctx_emerg()
# emergency ctx has always ksi 0
return self.output()
#
# check local ID
if self.UE.IMSI is None:
# UEd was created based on a PTMSI provided at the RRC layer
@ -408,16 +420,19 @@ class GMMAttach(GMMSigProc):
return self._ret_smc(Proc.cksn, True)
elif self._req_imeisv:
return self._ret_req_imeisv()
#
elif isinstance(Proc, RANAPSecurityModeControl):
if not Proc.success:
self.abort()
return None
return []
# self.Iu.SEC['CKSN'] has been taken into use at the RRC layer
elif self._req_imeisv:
return self._ret_req_imeisv()
#
elif Proc == self:
# something bad happened with one of the GMM common procedure
pass
#
elif Proc is not None:
self._err = Proc
assert()
@ -425,6 +440,10 @@ class GMMAttach(GMMSigProc):
return self.output()
def output(self):
if self.att_type == 3 and self.GMM.ATT_IMSI:
# IMSI attach not supported
self.errcause = self.GMM.ATT_IMSI
#
if self.errcause:
# prepare AttachReject IE
if self.errcause == self.GMM.ATT_IMSI_PROV_REJECT:
@ -437,8 +456,8 @@ class GMMAttach(GMMSigProc):
else:
# prepare AttachAccept IEs
IEs = {'ForceStdby' : {'Value': self.GMM.ATT_FSTDBY},
'AttachResult' : {'FollowOnProc': self.UEInfo['AttachType']['FollowOnReq'](),
'Result': self.UEInfo['AttachType']['Type']()},
'AttachResult' : {'FollowOnProc': self.UEInfo['AttachType']['FollowOnReq'].get_val(),
'Result': self.att_type},
'PeriodicRAUpdateTimer' : self.GMM.ATT_RAU_TIMER,
'RadioPrioForTOM8' : {'Value': self.GMM.ATT_PRIO_TOM8},
'RadioPrioForSMS' : {'Value': self.GMM.ATT_PRIO_SMS},
@ -455,7 +474,7 @@ class GMMAttach(GMMSigProc):
# but don't forward its output
if self.GMM.ATT_PTMSI_REALLOC:
NasProc = self.GMM.init_proc(GMMPTMSIReallocation)
void = NasProc.output(embedded=True)
NasProc.output(embedded=True)
IEs['AllocPTMSI'] = {'type': NAS.IDTYPE_TMSI, 'ident': NasProc.ptmsi}
self.ptmsi_realloc = NasProc.ptmsi
else:
@ -493,39 +512,27 @@ class GMMAttach(GMMSigProc):
#
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
ret = self.Iu.ret_ranap_dt(self._nas_tx)
if self.ptmsi_realloc < 0:
self._end(nas_tx=True)
ret.extend( self._end() )
else:
# use the timer of the GMMPTMSIRealloc
# and wait for a GMMAttachComplete to end the procedure
self.Timer = NasProc.Timer
self.init_timer()
# send Attach reject / accept
return self._nas_tx
return ret
def _end(self, nas_tx=True):
def _end(self):
ret = []
if self.GMM.ATT_IUREL and \
(self.errcause or not self.UEInfo['AttachType']['FollowOnReq']()):
# trigger an IuRelease after the direct transfer
RanapTx = []
if nas_tx:
try:
naspdu = self._nas_tx.to_bytes()
except Exception as err:
self._log('ERR', 'unable to encode downlink NAS PDU: %r' % err)
else:
RanapProcDT = self.Iu.init_ranap_proc(RANAPDirectTransferCN,
NAS_PDU=naspdu,
SAPI='sapi-0')
if RanapProcDT:
RanapTx.append( RanapProcDT )
# IuRelease with Cause NAS normal-release (83)
(self.errcause or not self.UEInfo['AttachType']['FollowOnReq'].get_val()):
# trigger an IuRelease with Cause NAS normal-release (83)
RanapProcRel = self.Iu.init_ranap_proc(RANAPIuRelease, Cause=('nAS', 83))
if RanapProcRel:
RanapTx.append( RanapProcRel )
if RanapTx:
self.Iu.RanapTx = RanapTx
ret.append(RanapProcRel)
self.rm_from_gmm_stack()
return ret
class GMMDetachUE(GMMSigProc):
@ -551,21 +558,6 @@ class GMMDetachUE(GMMSigProc):
(TS24008_GMM.GMMDetachRequestMO, )
)
def process(self, pdu):
# preempt the GMM stack
self.gmm_preempt()
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
if self.UEInfo['DetachTypeMO']['PowerOff']():
# if UE is to power-off, procedure ends here
ret = self.output(poff=True)
else:
ret = self.output()
self._detach()
return ret
def _detach(self):
# set GMM state
self.GMM.state = 'INACTIVE'
@ -576,33 +568,35 @@ class GMMDetachUE(GMMSigProc):
# abort all ongoing PS procedures
self.Iu.clear_nas_proc()
def process(self, pdu):
# preempt the GMM stack
self.gmm_preempt()
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
if self.UEInfo['DetachTypeMO']['PowerOff'].get_val():
# if UE is to power-off, procedure ends here
ret = self.output(poff=True)
else:
ret = self.output()
self._detach()
return ret
def output(self, poff=False):
# prepare a stack of RANAP procedure(s)
RanapTx = []
if not poff:
# set a RANAP direct transfer to transport the DetachAccept
self.set_msg(8, 6, ForceStdby={'Value': self.GMM.DET_FSTDBY})
self.encode_msg(8, 6)
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
try:
naspdu = self._nas_tx.to_bytes()
except Exception as err:
self._log('ERR', 'unable to encode downlink NAS PDU: %r' % err)
else:
RanapProcDT = self.Iu.init_ranap_proc(RANAPDirectTransferCN,
NAS_PDU=naspdu,
SAPI='sapi-0')
if RanapProcDT:
RanapTx.append( RanapProcDT )
RanapTxProc = self.Iu.ret_ranap_dt(self._nas_tx)
# set an Iu release with Cause NAS normal-release (83)
RanapProcRel = self.Iu.init_ranap_proc(RANAPIuRelease, Cause=('nAS', 83))
if RanapProcRel:
RanapTx.append( RanapProcRel )
#
if RanapTx:
self.Iu.RanapTx = RanapTx
return self._nas_tx
RanapTxProc.append( RanapProcRel )
return RanapTxProc
class GMMDetachCN(GMMSigProc):
@ -628,7 +622,6 @@ class GMMDetachCN(GMMSigProc):
Timer = 'T3322'
def output(self):
self.encode_msg(8, 5)
# log the NAS msg
@ -638,7 +631,7 @@ class GMMDetachCN(GMMSigProc):
self._log('INF', 'request type %r' % self._nas_tx['DetachTypeMT']['Type'])
self.init_timer()
# send it over RANAP
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
def process(self, pdu):
if self.TRACK_PDU:
@ -647,7 +640,7 @@ class GMMDetachCN(GMMSigProc):
#
self._log('INF', 'accepted')
self.rm_from_gmm_stack()
return None
return []
class GMMRoutingAreaUpdating(GMMSigProc):
@ -735,25 +728,25 @@ class GMMRoutingAreaUpdating(GMMSigProc):
(TS24008_GMM.GMMRoutingAreaUpdateRequest, TS24008_GMM.GMMRoutingAreaUpdateComplete)
)
Decod = {
(8, 8) : {
'CKSN' : lambda x: x(),
'OldRAI' : lambda x: (x['PLMN'].decode(), x['LAC'](), x['RAC']()),
'ReqREADYTimer' : lambda x: {'Unit': x[1]['Unit'](), 'Value': x[1]['Value']()},
}
}
Cap = ('MSRACap', 'DRXParam', 'MSNetCap', 'PSLCSCap', 'UENetCap', 'MSCm2',
'MSCm3', 'SuppCodecs', 'VoiceDomPref', 'DeviceProp', 'MSNetFeatSupp',
'ExtDRXParam')
Decod = {
(8, 8) : {
'CKSN' : lambda x: x.get_val(),
'OldRAI' : lambda x: (x['PLMN'].decode(), x['LAC'].get_val(), x['RAC'].get_val()),
'ReqREADYTimer' : lambda x: {'Unit': x[1]['Unit'].get_val(), 'Value': x[1]['Value'].get_val()},
}
}
def process(self, pdu):
# preempt the GMM stack
self.gmm_preempt()
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
if pdu['Type']() == 8:
if pdu['Type'].get_val() == 8:
# RoutingAreaUpdateRequest
self.errcause, self.UEInfo = None, {}
self.decode_msg(pdu, self.UEInfo)
@ -762,10 +755,10 @@ class GMMRoutingAreaUpdating(GMMSigProc):
# RoutingAreaUpdateComplete
self.errcause, self.CompInfo = None, {}
self.decode_msg(pdu, self.CompInfo)
self.UE.set_ptmsi(self.ptmsi_realloc)
self._log('INF', 'new P-TMSI set, 0x%.8x' % self.ptmsi_realloc)
self._end(nas_tx=False)
return None
if self.ptmsi_realloc >= 0:
self.UE.set_ptmsi(self.ptmsi_realloc)
self._log('INF', 'new P-TMSI set, 0x%.8x' % self.ptmsi_realloc)
return self._end()
def _process_req(self):
#
@ -819,19 +812,23 @@ class GMMRoutingAreaUpdating(GMMSigProc):
# if we are here, there was no auth procedure,
# hence the cksn submitted by the UE is valid
return self._ret_smc(self.UEInfo['CKSN'], False)
#
if isinstance(Proc, GMMAuthenticationCiphering):
if self.Iu.require_smc(self):
# if we are here, the valid cksn is the one established during
# the auth procedure
return self._ret_smc(Proc.cksn, True)
#
elif isinstance(Proc, RANAPSecurityModeControl):
if not Proc.success:
self.abort()
return None
return []
# self.Iu.SEC['CKSN'] has been taken into use at the RRC layer
#
elif Proc == self:
# something bad happened with one of the GMM common procedure
pass
#
elif Proc is not None:
self._err = Proc
assert()
@ -851,8 +848,8 @@ class GMMRoutingAreaUpdating(GMMSigProc):
else:
# prepare RAUAccept IEs
IEs = {'ForceStdby' : {'Value': self.GMM.RAU_FSTDBY},
'UpdateResult' : {'FollowOnProc': self.UEInfo['UpdateType']['FollowOnReq'](),
'Value': self.UEInfo['UpdateType']['Value']()},
'UpdateResult' : {'FollowOnProc': self.UEInfo['UpdateType']['FollowOnReq'].get_val(),
'Value': self.UEInfo['UpdateType']['Value'].get_val()},
'PeriodicRAUpdateTimer' : self.GMM.RAU_RAU_TIMER,
'RAI' : {'PLMN': self.UE.PLMN, 'LAC': self.UE.LAC, 'RAC': self.UE.RAC}
}
@ -905,39 +902,27 @@ class GMMRoutingAreaUpdating(GMMSigProc):
#
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
ret = self.Iu.ret_ranap_dt(self._nas_tx)
if self.ptmsi_realloc < 0:
self._end(nas_tx=True)
ret.extend( self._end() )
else:
# use the timer of the GMMPTMSIRealloc
# and wait for a GMMRAUComplete to end the procedure
# and wait for a GMMAttachComplete to end the procedure
self.Timer = NasProc.Timer
self.init_timer()
# send RAU reject / accept
return self._nas_tx
# send Attach reject / accept
return ret
def _end(self, nas_tx=True):
def _end(self):
ret = []
if self.GMM.RAU_IUREL and \
(self.errcause or not self.UEInfo['UpdateType']['FollowOnReq']()):
# trigger an IuRelease after the direct transfer
RanapTx = []
if nas_tx:
try:
naspdu = self._nas_tx.to_bytes()
except Exception as err:
self._log('ERR', 'unable to encode downlink NAS PDU: %r' % err)
else:
RanapProcDT = self.Iu.init_ranap_proc(RANAPDirectTransferCN,
NAS_PDU=naspdu,
SAPI='sapi-0')
if RanapProcDT:
RanapTx.append( RanapProcDT )
# IuRelease with Cause NAS normal-release (83)
(self.errcause or not self.UEInfo['UpdateType']['FollowOnReq'].get_val()):
# trigger an IuRelease with Cause NAS normal-release (83)
RanapProcRel = self.Iu.init_ranap_proc(RANAPIuRelease, Cause=('nAS', 83))
if RanapProcRel:
RanapTx.append( RanapProcRel )
if RanapTx:
self.Iu.RanapTx = RanapTx
ret.append(RanapProcRel)
self.rm_from_gmm_stack()
return ret
class GMMPTMSIReallocation(GMMSigProc):
@ -983,12 +968,12 @@ class GMMPTMSIReallocation(GMMSigProc):
#
self.init_timer()
# send it over RANAP
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
else:
# when the P-TMSI realloc is embedded, there is no realloc complete
# to expect...
self.rm_from_gmm_stack()
return None
return []
def process(self, pdu):
if self.TRACK_PDU:
@ -1000,7 +985,7 @@ class GMMPTMSIReallocation(GMMSigProc):
self.UE.set_ptmsi(self.ptmsi)
self._log('INF', 'new P-TMSI set, 0x%.8x' % self.ptmsi)
self.rm_from_gmm_stack()
return None
return []
class GMMAuthenticationCiphering(GMMSigProc):
@ -1048,12 +1033,12 @@ class GMMAuthenticationCiphering(GMMSigProc):
Decod = {
(8, 19): {
'RES' : lambda x: x['V'](),
'RES' : lambda x: x['V'].get_val(),
'IMEISV' : lambda x: x[2].decode(),
'RESExt' : lambda x: x['V']()
'RESExt' : lambda x: x['V'].get_val()
},
(8, 28): {
'AUTS' : lambda x: x['V']()
'AUTS' : lambda x: x['V'].get_val()
}
}
@ -1080,7 +1065,7 @@ class GMMAuthenticationCiphering(GMMSigProc):
# IMSI is not in the AuC db
self._log('ERR', 'unable to get an authentication vector from AuC')
self.rm_from_gmm_stack()
return None
return []
#
# prepare IEs
IEs = {'IMEISVReq' : self.GMM.AUTH_IMEI_REQ,
@ -1104,7 +1089,7 @@ class GMMAuthenticationCiphering(GMMSigProc):
#
self.init_timer()
# send it over RANAP
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
def process(self, pdu):
if self.TRACK_PDU:
@ -1112,7 +1097,7 @@ class GMMAuthenticationCiphering(GMMSigProc):
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
#
if pdu['Type']() == 19:
if pdu['Type'].get_val() == 19:
return self._process_resp()
else:
return self._process_fail()
@ -1130,10 +1115,10 @@ class GMMAuthenticationCiphering(GMMSigProc):
% (hexlify(self.vect[1]).decode('ascii'),
hexlify(res).decode('ascii')))
self.encode_msg(8, 20)
rej = True
self.success = False
else:
self._log('DBG', '3G authentication accepted')
rej = False
self.success = True
# set a 3G security context
self.Iu.set_sec_ctx(self.cksn, 3, self.vect)
else:
@ -1144,23 +1129,26 @@ class GMMAuthenticationCiphering(GMMSigProc):
% (hexlify(self.vect[1]).decode('ascii'),
hexlify(self.UEInfo['RES']).decode('ascii')))
self.encode_msg(8, 20)
rej = True
self.success = False
else:
self._log('DBG', '2G authentication accepted')
rej = False
self.success = True
# set a 2G security context
self.Iu.set_sec_ctx(self.cksn, 2, self.vect)
#
self.rm_from_gmm_stack()
if rej:
return self._nas_tx
if not self.success:
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
return self.Iu.ret_ranap_dt(self._nas_tx)
else:
if 'IMEISV' in self.UEInfo:
self.UE.set_ident_from_ue(NAS.IDTYPE_IMEISV, self.UEInfo['IMEISV'])
return None
return []
def _process_fail(self):
if self.UEInfo['GMMCause']() == 21 and 'AUTS' in self.UEInfo:
self.success = False
if self.UEInfo['GMMCause'].get_val() == 21 and 'AUTS' in self.UEInfo:
# synch failure
# resynchronize the SQN in case the MM stack is not already doing it
if self.UE.IuCS is not None and self.UE.IuCS.MM.state == 'ACTIVE' \
@ -1174,14 +1162,18 @@ class GMMAuthenticationCiphering(GMMSigProc):
self._log('ERR', 'unable to resynchronize SQN in AuC')
self.encode_msg(8, 20)
self.rm_from_gmm_stack()
return self._nas_tx
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
return self.Iu.ret_ranap_dt(self._nas_tx)
#
elif ret:
# USIM did not authenticate correctly
self._log('WNG', 'USIM authentication failed for resynch')
self.encode_msg(8, 20)
self.rm_from_gmm_stack()
return self._nas_tx
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
return self.Iu.ret_ranap_dt(self._nas_tx)
#
else:
# resynch OK: restart an auth procedure
@ -1195,7 +1187,7 @@ class GMMAuthenticationCiphering(GMMSigProc):
# UE refused our auth request...
self._log('ERR', 'UE rejected AUTN, %s' % self.UEInfo['Cause'])
self.rm_from_gmm_stack()
return None
return []
class GMMIdentification(GMMSigProc):
@ -1236,7 +1228,7 @@ class GMMIdentification(GMMSigProc):
#
self.init_timer()
# send it
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
def process(self, pdu):
if self.TRACK_PDU:
@ -1244,7 +1236,7 @@ class GMMIdentification(GMMSigProc):
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
# get the identity IE value
self.IDType = self._nas_tx['IDType']()
self.IDType = self._nas_tx['IDType'].get_val()
#
if self.UEInfo['ID'][0] != self.IDType :
self._log('WNG', 'identity responded not corresponding to type requested '\
@ -1253,7 +1245,7 @@ class GMMIdentification(GMMSigProc):
self.UE.set_ident_from_ue(*self.UEInfo['ID'], dom='PS')
#
self.rm_from_gmm_stack()
return None
return []
class GMMInformation(GMMSigProc):
@ -1290,7 +1282,7 @@ class GMMInformation(GMMSigProc):
self._log('INF', '%r' % self.Encod[(8, 33)])
self.rm_from_gmm_stack()
# send it
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
class GMMServiceRequest(GMMSigProc):
@ -1325,7 +1317,7 @@ class GMMServiceRequest(GMMSigProc):
Decod = {
(8, 12): {
'CKSN' : lambda x: x(),
'CKSN' : lambda x: x.get_val(),
'PTMSI' : lambda x: x[1].decode(),
'PDPCtxtStat' : lambda x: {c: x[2]['NSAPI_%i' % c] for c in range(0, 16)},
'MBMSCtxtStat': lambda x: {c: x[2]['NSAPI_%i' % c] for c in range(0, 16)},
@ -1360,7 +1352,7 @@ class GMMServiceRequest(GMMSigProc):
elif isinstance(Proc, RANAPSecurityModeControl):
if not Proc.success:
self.abort()
return None
return []
# self.Iu.SEC['CKSN'] has been taken into use at the RRC layer
elif Proc == self:
# something bad happened with one of the GMM common procedure
@ -1382,7 +1374,7 @@ class GMMServiceRequest(GMMSigProc):
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
self.rm_from_gmm_stack()
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
# filter_init=1, indicates we are the core network side
@ -1403,6 +1395,8 @@ GMMProcUeDispatcher = {
8 : GMMRoutingAreaUpdating,
12: GMMServiceRequest
}
GMMProcUeDispatcherStr = {ProcClass.Cont[1][0]()._name: ProcClass \
for ProcClass in GMMProcUeDispatcher.values()}
# GMM CN-initiated procedures dispatcher
GMMProcCnDispatcher = {
@ -1412,3 +1406,6 @@ GMMProcCnDispatcher = {
21: GMMIdentification,
33: GMMInformation,
}
GMMProcCnDispatcherStr = {ProcClass.Cont[0][0]()._name: ProcClass \
for ProcClass in GMMProcCnDispatcher.values()}

View File

@ -31,13 +31,13 @@ from .utils import *
from .ProcProto import *
#------------------------------------------------------------------------------#
# HNBAP signaling procedure
# HNBAP signalling procedure
# TS 25.469, version d10
# HNB-GW side
#------------------------------------------------------------------------------#
class HNBAPSigProc(LinkSigProc):
"""HNBAP signaling procedure handler
"""HNBAP signalling procedure handler
instance attributes:
- Name : procedure name
@ -62,22 +62,37 @@ class HNBAPSigProc(LinkSigProc):
# to store PDU traces
self._pdu = []
# list of PDU to be sent to the HNB
self._snd = []
self._pdu_tx = []
#
self._log('DBG', 'instantiating procedure')
def _log(self, logtype, msg):
self.HNB._log(logtype, '[%s] %s' % (self.Name, msg))
def recv(self, pdu):
def _recv(self, pdu_rx):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self._pdu.append( (time(), 'UL', pdu_rx) )
self.errcause, self.HNBInfo = None, {}
try:
self.decode_pdu(pdu_rx, self.HNBInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu (%s), sending error indication' % err)
self.errcause = ('protocol', 'abstract-syntax-error-reject')
def recv(self, pdu_rx):
self._recv(pdu_rx)
self._log('ERR', 'recv() not implemented')
def _send(self):
if self.TRACK_PDU:
for pdu in self._pdu_tx:
self._pdu.append( (time(), 'DL', pdu) )
return self._pdu_tx
def send(self):
self._log('ERR', 'send() not implemented')
return self._snd
return self._send()
def trigger(self):
self._log('ERR', 'trigger() not implemented')
@ -154,42 +169,19 @@ class HNBAPHNBRegistration(HNBAPSigProc):
'uns': ({}, {})
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
errcause = None
self.HNB.Config.clear()
# use the PDU to populate the Config of the HNBd
try:
self.decode_pdu(pdu, self.HNB.Config)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
self.HNB.Config.clear()
errcause = ('protocol', 'abstract-syntax-error-reject')
#
if errcause is None:
# procedure successful outcome
self.HNB.ID = (self.HNB.Config['PLMNidentity'], self.HNB.Config['CellIdentity'])
def recv(self, pdu_rx):
self._recv(pdu_rx)
if self.errcause:
# procedure unsuccessful outcome
self.encode_pdu('uns', Cause=self.errcause)
self._log('INF', 'HNB not registered successfully')
else:
self.HNB.Config = cpdict(self.HNBInfo)
self.HNB.ID = (self.HNBInfo['PLMNidentity'], self.HNBInfo['CellIdentity'])
self.encode_pdu('suc', RNC_ID=self.HNB.RNC_ID)
self._log('INF', 'HNB registered successfully')
else:
# procedure unsuccessful outcome
self.encode_pdu('uns', Cause=errcause)
self._log('INF', 'HNB not registered successfully')
def send(self):
if self.TRACK_PDU:
for pdu in self._snd:
self._pdu.append( (time(), 'DL', pdu) )
# remove from the HNB HNBAP procedure stack
try:
del self.HNB.ProcHnbap[self.Code]
except:
pass
# send back the list of PDU to be returned to the HNB
return self._snd
send = HNBAPSigProc._send
class HNBAPHNBDeregistrationHNB(HNBAPSigProc):
@ -222,6 +214,13 @@ class HNBAPHNBDeregistrationHNB(HNBAPSigProc):
'suc': None,
'uns': None
}
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
# remove the HNB from the Server.LAC / RAC dict
self.Server._unset_hnb_loc(self)
self._log('INF', 'HNB deregistered')
class HNBAPHNBDeregistrationGW(HNBAPSigProc):
@ -254,6 +253,8 @@ class HNBAPHNBDeregistrationGW(HNBAPSigProc):
'suc': None,
'uns': None
}
# not implemented
class HNBAPUERegistration(HNBAPSigProc):
@ -301,21 +302,11 @@ class HNBAPUERegistration(HNBAPSigProc):
'uns': ({}, {})
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
errcause, UEInfo = None, {}
try:
self.decode_pdu(pdu, UEInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
errcause = ('protocol', 'abstract-syntax-error-reject')
#
if errcause is None:
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
# get the UE identity (IMSI or TMSI)
ue, UEId = None, UEInfo['UE_Identity']
ue, UEId = None, self.HNBInfo['UE_Identity']
if UEId[0] == 'iMSI':
imsi = TS24008_IE.decode_bcd(UEId[1])
ue = self.Server.get_ued(imsi=imsi)
@ -328,39 +319,27 @@ class HNBAPUERegistration(HNBAPSigProc):
else:
self._log('WNG', 'unsupported UE identity, %r' % UEId)
# unsupported UE identity
errcause = ('radioNetwork', 'invalid-UE-identity')
self.errcause = ('radioNetwork', 'invalid-UE-identity')
#
if ue is None:
# UE not allowed / configured in the CorenetServer
errcause = self.HNB.UEREG_NOTALLOWED
self.errcause = self.HNB.UEREG_NOTALLOWED
else:
ctx_id = self.HNB.set_ue_hnbap(ue)
if 'UE_Capabilities' in UEInfo:
ue.Cap['HNBAP'] = UEInfo['UE_Capabilities']
if 'UE_Capabilities' in self.HNBInfo:
ue.Cap['HNBAP'] = self.HNBInfo['UE_Capabilities']
#
if errcause is None:
# procedure successful outcome
# both IuCS / IuPS are initialized with the same CtxId established here,
# at the HNBAP layer, so we can take the IuCS one safely
self.encode_pdu('suc', Context_ID=(ctx_id, 24),
UE_Identity=UEInfo['UE_Identity'])
self._log('INF', 'UE registered successfully, ctx %i' % ue.IuCS.CtxId)
else:
if self.errcause:
# procedure unsuccessful outcome
self.encode_pdu('uns', Cause=errcause,
UE_Identity=UEInfo['UE_Identity'])
self.encode_pdu('uns', Cause=self.errcause,
UE_Identity=self.HNBInfo['UE_Identity'])
self._log('INF', 'UE not registered successfully')
else:
self.encode_pdu('suc', Context_ID=(ctx_id, 24),
UE_Identity=self.HNBInfo['UE_Identity'])
self._log('INF', 'UE registered successfully, ctx %i' % ue.IuCS.CtxId)
def send(self):
if self.TRACK_PDU:
for pdu in self._snd:
self._pdu.append( (time(), 'DL', pdu) )
# remove from the HNB HNBAP procedure stack
try:
del self.HNB.ProcHnbap[self.Code]
except:
pass
# send back the list of PDU to be returned to the HNB
return self._snd
send = HNBAPSigProc._send
class HNBAPUEDeregistrationHNB(HNBAPSigProc):
@ -394,27 +373,11 @@ class HNBAPUEDeregistrationHNB(HNBAPSigProc):
'uns': None
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
errcause, UEInfo = None, {}
try:
self.decode_pdu(pdu, UEInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
errcause = ('protocol', 'abstract-syntax-error-reject')
#
if errcause is None:
# UE RAN should have been unset through RUA / RANAP procedures
self.HNB.unset_ue_hnbap(UEInfo['Context_ID'][0])
#
# remove from the HNB HNBAP procedure stack
try:
del self.HNB.ProcHnbap[self.Code]
except:
pass
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
self.HNB.unset_ue_hnbap(self.HNBInfo['Context_ID'][0])
# UE IuCS / IuPS handlers should have been unset through RANAP procedures
class HNBAPUEDeregistrationGW(HNBAPSigProc):
@ -447,6 +410,8 @@ class HNBAPUEDeregistrationGW(HNBAPSigProc):
'suc': None,
'uns': None
}
# not implemented
class HNBAPErrorIndHNB(HNBAPSigProc):
@ -480,34 +445,16 @@ class HNBAPErrorIndHNB(HNBAPSigProc):
'uns': None
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
self.ErrInfo = {}
try:
self.decode_pdu(pdu, self.ErrInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
# do not respond to an error ind, with another error ind...
else:
self._log('WNG', 'error indication received: %s.%s' % self.ErrInfo['Cause'])
# this means the HNB failed to process the previous msg sent to it
code = self.HNB.ProcHnbapLast
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
self._log('WNG', 'error ind received: %s.%s' % self.HNBInfo['Cause'])
# if it corresponds to a previously CN-initiated class 1 procedure
# abort it
try:
Proc = self.HNB.ProcHnbap[code]
self.ProcHnbap[self.HNB.ProcHnbapLast].abort()
except:
pass
else:
# abort the corresponding running procedure
Proc.abort()
#
# remove from the HNB HNBAP procedure stack
try:
del self.HNB.ProcHnbap[self.Code]
except:
pass
class HNBAPErrorIndGW(HNBAPSigProc):
@ -541,19 +488,13 @@ class HNBAPErrorIndGW(HNBAPSigProc):
'uns': None
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
# this means we are not able to process a request received from the HNB
# this is handled directly within the HNBHdlr instance
errcause = None
def send(self):
def recv(self, pdu_rx):
if self.TRACK_PDU:
for pdu in self._snd:
self._pdu.append( (time(), 'DL', pdu) )
# send back the list of PDU to be returned to the HNB
return self._snd
self._pdu.append( (time(), 'UL', pdu_rx) )
send = HNBAPSigProc._send
class HNBAPCSGMembershipUpdate(HNBAPSigProc):
@ -586,6 +527,8 @@ class HNBAPCSGMembershipUpdate(HNBAPSigProc):
'suc': None,
'uns': None
}
# not implemented
class HNBAPTNLUpdate(HNBAPSigProc):
@ -631,6 +574,8 @@ class HNBAPTNLUpdate(HNBAPSigProc):
'suc': ({}, {}),
'uns': ({}, {})
}
# not implemented
class HNBAPHNBConfigTransfer(HNBAPSigProc):
@ -667,6 +612,8 @@ class HNBAPHNBConfigTransfer(HNBAPSigProc):
'suc': ({}, {}),
'uns': None
}
# not implemented
class HNBAPRelocationComplete(HNBAPSigProc):
@ -698,6 +645,8 @@ class HNBAPRelocationComplete(HNBAPSigProc):
'suc': None,
'uns': None
}
# not implemented
class HNBAPURNTIQuery(HNBAPSigProc):
@ -734,6 +683,8 @@ class HNBAPURNTIQuery(HNBAPSigProc):
'suc': ({}, {}),
'uns': None
}
# not implemented
class HNBAPPrivateMessageHNB(HNBAPSigProc):
@ -762,6 +713,8 @@ class HNBAPPrivateMessageHNB(HNBAPSigProc):
'suc': None,
'uns': None
}
# not implemented
class HNBAPPrivateMessageGW(HNBAPSigProc):
@ -790,8 +743,11 @@ class HNBAPPrivateMessageGW(HNBAPSigProc):
'suc': None,
'uns': None
}
# not implemented
# initializing all HNBAP procedures classes
HNBAPHNBRegistration.init()
HNBAPHNBDeregistrationHNB.init()
HNBAPUERegistration.init()
@ -830,3 +786,4 @@ HNBAPProcGwDispatcher = {
7 : HNBAPCSGMembershipUpdate,
11 : HNBAPRelocationComplete
}

View File

@ -35,20 +35,20 @@ from .ProcCNRanap import *
TESTING = False
#------------------------------------------------------------------------------#
# NAS Mobility Management signaling procedure
# NAS Mobility Management signalling procedures
# TS 24.008, version d90
# Core Network side
#------------------------------------------------------------------------------#
class MMSigProc(NASSigProc):
"""Mobility Management signaling procedure handler
"""Mobility Management signalling procedure handler
instance attributes:
- Name : procedure name
- MM : reference to the UEMMd instance running this procedure
- Iu : reference to the IuCSd instance connecting the UE
- Cont : 2-tuple of CN-initiated NAS message(s) and UE-initiated NAS
message(s)
message(s)
- Timer: timer in sec. for this procedure
- Encod: custom NAS message encoders with fixed values
- Decod: custom NAS message decoders with transform functions
@ -86,7 +86,7 @@ class MMSigProc(NASSigProc):
def output(self):
self._log('ERR', 'output() not implemented')
return None
return []
def process(self, pdu):
if self.TRACK_PDU:
@ -95,11 +95,11 @@ class MMSigProc(NASSigProc):
self.decode_msg(pdu, self.UEInfo)
#
self._log('ERR', 'process() not implemented')
return None
return []
def postprocess(self, Proc=None):
self._log('ERR', 'postprocess() not implemented')
return None
return []
def abort(self):
# abort this procedure, and all procedures started within this one
@ -148,6 +148,53 @@ class MMSigProc(NASSigProc):
if Cap in self.UEInfo:
self.UE.Cap[Cap] = self.UEInfo[Cap]
def _chk_imsi(self):
# arriving here means the UE's IMSI was unknown at first
Server, imsi = self.UE.Server, self.UE.IMSI
if not Server.is_imsi_allowed(imsi):
self.errcause = self.MM.IDENT_IMSI_NOT_ALLOWED
return False
else:
# update the TMSI table
Server.TMSI[self.UE.TMSI] = imsi
#
if imsi in Server.UE:
# in the meantime, IMSI was obtained from the PS domain connection
if self.UE != Server.UE[imsi]:
# there is 2 distincts Iu contexts, that need to be merged
ue = Server.UE[imsi]
if not ue.merge_cs_handler(self.Iu):
# unable to merge to the existing profile
self._log('WNG', 'profile for IMSI %s already exists, '\
'need to reject for reconnection' % imsi)
# reject so that it will reconnect
# and get the already existing profile
self.errcause = self.MM.LU_IMSI_PROV_REJECT
return False
else:
return True
else:
return True
else:
# update the Server UE's tables
Server.UE[imsi] = self.UE
if imsi in Server.ConfigUE:
# update UE's config with it's dedicated config
self.UE.set_config( Server.ConfigUE[imsi] )
else:
self.UE.set_config( Server.ConfigUE['*'] )
return True
def _ret_req_imsi(self):
NasProc = self.MM.init_proc(MMIdentification)
NasProc.set_msg(5, 24, IDType=NAS.IDTYPE_IMSI)
return NasProc.output()
def _ret_req_imei(self):
NasProc = self.MM.init_proc(MMIdentification)
NasProc.set_msg(5, 24, IDType=NAS.IDTYPE_IMEI)
return NasProc.output()
def _ret_auth(self):
NasProc = self.MM.init_proc(MMAuthentication)
return NasProc.output()
@ -156,12 +203,13 @@ class MMSigProc(NASSigProc):
# initialize a RANAP SMC procedure
RanapProc = self.Iu.init_ranap_proc(RANAPSecurityModeControl,
**self.Iu.get_smc_ies(cksn, newkey))
# and set a callback to self in it
RanapProc._cb = self
if RanapProc:
self.Iu.RanapTx = [RanapProc]
return None
# and set a callback to self in it
RanapProc._cb = self
return [RanapProc]
else:
return []
#------------------------------------------------------------------------------#
# MM common procedures: TS 24.008, section 4.3
@ -207,10 +255,10 @@ class MMTMSIReallocation(MMSigProc):
#
self.init_timer()
# send it over RANAP
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
else:
self.init_timer()
return None
return []
def process(self, pdu):
if self.TRACK_PDU:
@ -222,7 +270,7 @@ class MMTMSIReallocation(MMSigProc):
self.UE.set_tmsi(self.tmsi)
self._log('INF', 'new TMSI set, 0x%.8x' % self.tmsi)
self.rm_from_mm_stack()
return None
return []
class MMAuthentication(MMSigProc):
@ -262,11 +310,11 @@ class MMAuthentication(MMSigProc):
Decod = {
(5, 20): {
'RES' : lambda x: x(),
'RESExt': lambda x: x['V']()
'RES' : lambda x: x.get_val(),
'RESExt': lambda x: x['V'].get_val()
},
(5, 28): {
'AUTS' : lambda x: x['V']()
'AUTS' : lambda x: x['V'].get_val()
}
}
@ -293,7 +341,7 @@ class MMAuthentication(MMSigProc):
# IMSI is not in the AuC db
self._log('ERR', 'unable to get an authentication vector from AuC')
self.rm_from_mm_stack()
return None
return []
#
if self.ctx == 2:
# msg without AUTN
@ -312,7 +360,7 @@ class MMAuthentication(MMSigProc):
#
self.init_timer()
# send it over RANAP
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
def process(self, pdu):
if self.TRACK_PDU:
@ -324,7 +372,7 @@ class MMAuthentication(MMSigProc):
if hasattr(self.MM, '_auth_resynch'):
del self.MM._auth_resynch
#
if pdu['Type']() == 20:
if pdu['Type'].get_val() == 20:
return self._process_resp()
else:
return self._process_fail()
@ -365,12 +413,12 @@ class MMAuthentication(MMSigProc):
#
self.rm_from_mm_stack()
if rej:
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
else:
return None
return []
def _process_fail(self):
if self.UEInfo['Cause']() == 21 and 'AUTS' in self.UEInfo:
if self.UEInfo['Cause'].get_val() == 21 and 'AUTS' in self.UEInfo:
# synch failure: resynchronize the SQN
# set an indicator to avoid the PS stack to do another resynch
self.MM._auth_resynch = True
@ -380,14 +428,14 @@ class MMAuthentication(MMSigProc):
self._log('ERR', 'unable to resynchronize SQN in AuC')
self.encode_msg(5, 17)
self.rm_from_mm_stack()
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
#
elif ret:
# USIM did not authenticate correctly
self._log('WNG', 'USIM authentication failed for resynch')
self.encode_msg(5, 17)
self.rm_from_mm_stack()
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
#
else:
# resynch OK: restart an auth procedure
@ -401,7 +449,7 @@ class MMAuthentication(MMSigProc):
# UE refused our auth request...
self._log('ERR', 'UE rejected AUTN, %s' % self.UEInfo['Cause'])
self.rm_from_mm_stack()
return None
return []
class MMIdentification(MMSigProc):
@ -433,7 +481,7 @@ class MMIdentification(MMSigProc):
Decod = {
(5, 25): {
'ID' : lambda x: x[1].decode(),
'PTMSIType': lambda x: x[1](),
'PTMSIType': lambda x: x[1].get_val(),
'RAI' : lambda x: x[2].decode()
}
}
@ -447,7 +495,7 @@ class MMIdentification(MMSigProc):
#
self.init_timer()
# send it
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
def process(self, pdu):
if self.TRACK_PDU:
@ -455,7 +503,7 @@ class MMIdentification(MMSigProc):
self.UEInfo = {}
self.decode_msg(pdu, self.UEInfo)
# get the identity IE value
self.IDType = self._nas_tx['IDType']()
self.IDType = self._nas_tx['IDType'].get_val()
#
if self.UEInfo['ID'][0] != self.IDType:
self._log('WNG', 'identity responded not corresponding to type requested '\
@ -464,7 +512,7 @@ class MMIdentification(MMSigProc):
self.UE.set_ident_from_ue(*self.UEInfo['ID'], dom='CS')
#
self.rm_from_mm_stack()
return None
return []
class MMIMSIDetach(MMSigProc):
@ -515,8 +563,9 @@ class MMIMSIDetach(MMSigProc):
# with Cause NAS normal-release (83)
RanapProc = self.Iu.init_ranap_proc(RANAPIuRelease, Cause=('nAS', 83))
if RanapProc:
self.Iu.RanapTx = [RanapProc]
return None
return [RanapProc]
else:
return []
class MMAbort(MMSigProc):
@ -546,7 +595,7 @@ class MMAbort(MMSigProc):
self._pdu.append( (time(), 'DL', self._nas_tx) )
self.rm_from_mm_stack()
# send it
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
class MMInformation(MMSigProc):
@ -584,7 +633,7 @@ class MMInformation(MMSigProc):
self._log('INF', '%r' % self.Encod[(5, 50)])
self.rm_from_mm_stack()
# send it
return self._nas_tx
return self.Iu.ret_ranap_dt(self._nas_tx)
#------------------------------------------------------------------------------#
@ -629,21 +678,16 @@ class MMLocationUpdating(MMSigProc):
(TS24008_MM.MMLocationUpdatingRequest, )
)
Cap = ('MSCm1', 'MSCm2", "AddUpdateParams', 'DeviceProp', 'MSNetFeatSupp')
Decod = {
(5, 8): {
'CKSN' : lambda x: x(),
'LAI' : lambda x: (x['PLMN'].decode(), x['LAC']()),
'ID' : lambda x: x[1].decode(),
'MSCm2' : lambda x: x[2],
'AddUpdateParams': lambda x: x[1],
'DeviceProp' : lambda x: x[1],
'MSNetFeatSupp' : lambda x: x[1],
'CKSN' : lambda x: x.get_val(),
'LAI' : lambda x: (x['PLMN'].decode(), x['LAC'].get_val()),
'ID' : lambda x: x[1].decode(),
}
}
# UE capabilities to be collected
Cap = ('MSCm1', 'MSCm2", "AddUpdateParams', 'DeviceProp', 'MSNetFeatSupp')
def process(self, pdu):
# got an MMLocationUpdatingRequest
# preempt the MM stack
@ -701,53 +745,6 @@ class MMLocationUpdating(MMSigProc):
# otherwise, go directly to postprocess
return self.postprocess()
def _chk_imsi(self):
# arriving here means the UE's IMSI was unknown at first
Server, imsi = self.UE.Server, self.UE.IMSI
if not Server.is_imsi_allowed(imsi):
self.errcause = self.MM.IDENT_IMSI_NOT_ALLOWED
return False
else:
# update the TMSI table
Server.TMSI[self.UE.TMSI] = imsi
#
if imsi in Server.UE:
# in the meantime, IMSI was obtained from the PS domain connection
if self.UE != Server.UE[imsi]:
# there is 2 distincts Iu contexts, that need to be merged
ue = Server.UE[imsi]
if not ue.merge_cs_handler(self.Iu):
# unable to merge to the existing profile
self._log('WNG', 'profile for IMSI %s already exists, '\
'need to reject for reconnection' % imsi)
# reject so that it will reconnect
# and get the already existing profile
self.errcause = self.MM.LU_IMSI_PROV_REJECT
return False
else:
return True
else:
return True
else:
# update the Server UE's tables
Server.UE[imsi] = self.UE
if imsi in Server.ConfigUE:
# update UE's config with it's dedicated config
self.UE.set_config( Server.ConfigUE[imsi] )
else:
self.UE.set_config( Server.ConfigUE['*'] )
return True
def _ret_req_imsi(self):
NasProc = self.MM.init_proc(MMIdentification)
NasProc.set_msg(5, 24, IDType=NAS.IDTYPE_IMSI)
return NasProc.output()
def _ret_req_imei(self):
NasProc = self.MM.init_proc(MMIdentification)
NasProc.set_msg(5, 24, IDType=NAS.IDTYPE_IMEI)
return NasProc.output()
def postprocess(self, Proc=None):
if isinstance(Proc, MMIdentification):
if Proc.IDType == NAS.IDTYPE_IMSI:
@ -785,16 +782,14 @@ class MMLocationUpdating(MMSigProc):
elif isinstance(Proc, RANAPSecurityModeControl):
if not Proc.success:
self.abort()
self._end(nas_tx=False)
return None
return self._end()
# self.Iu.SEC['CKSN'] has been taken into use at the RRC layer
elif self._req_imei:
return self._ret_req_imei()
#
elif isinstance(Proc, MMTMSIReallocation):
# everything went fine, end of the procedure
self._end(nas_tx=False)
return None
return self._end()
#
elif Proc == self:
# something bad happened with one of the MM common procedure
@ -848,34 +843,21 @@ class MMLocationUpdating(MMSigProc):
#
if self.TRACK_PDU:
self._pdu.append( (time(), 'DL', self._nas_tx) )
ret = self.Iu.ret_ranap_dt(self._nas_tx)
if not self.tmsi_realloc:
self._end(nas_tx=True)
# send LU reject / accept
return self._nas_tx
ret.extend( self._end() )
return ret
def _end(self, nas_tx=True):
def _end(self):
ret = []
if self.MM.LU_IUREL and \
(self.errcause or not self.UEInfo['LocUpdateType']['FollowOnReq']()):
# trigger an IuRelease after the direct transfer
RanapTx = []
if nas_tx:
try:
naspdu = self._nas_tx.to_bytes()
except Exception as err:
self._log('ERR', 'unable to encode downlink NAS PDU: %r' % err)
else:
RanapProcDT = self.Iu.init_ranap_proc(RANAPDirectTransferCN,
NAS_PDU=naspdu,
SAPI='sapi-0')
if RanapProcDT:
RanapTx.append( RanapProcDT )
# IuRelease with Cause NAS normal-release (83)
(self.errcause or not self.UEInfo['LocUpdateType']['FollowOnReq'].get_val()):
# trigger an IuRelease with Cause NAS normal-release (83)
RanapProcRel = self.Iu.init_ranap_proc(RANAPIuRelease, Cause=('nAS', 83))
if RanapProcRel:
RanapTx.append( RanapProcRel )
if RanapTx:
self.Iu.RanapTx = RanapTx
ret.append(RanapProcRel)
self.rm_from_mm_stack()
return ret
class RRPagingResponse(MMSigProc):
@ -900,10 +882,8 @@ class RRPagingResponse(MMSigProc):
Decod = {
(6, 39): {
'CKSN' : lambda x: x(),
'MSCm2' : lambda x: x[1],
'CKSN' : lambda x: x.get_val(),
'ID' : lambda x: x[1].decode(),
'AddUpdateParams': lambda x: x[1],
}
}
@ -935,7 +915,7 @@ class RRPagingResponse(MMSigProc):
elif isinstance(Proc, RANAPSecurityModeControl):
if not Proc.success:
self.abort()
return None
return []
# self.Iu.SEC['CKSN'] has been taken into use at the RRC layer
elif Proc == self:
# something bad happened with one of the MM common procedure
@ -945,7 +925,7 @@ class RRPagingResponse(MMSigProc):
assert()
#
self.rm_from_mm_stack()
return None
return []
#------------------------------------------------------------------------------#
@ -1075,6 +1055,9 @@ MMProcUeDispatcher = {
36: MMConnectionEstablishment,
40: MMCMCallReestablishment
}
MMProcUeDispatcherStr = {ProcClass.Cont[1][0]()._name: ProcClass \
for ProcClass in MMProcUeDispatcher.values()}
MMProcUeDispatcherStr['RRPagingResponse'] = RRPagingResponse
# MM CN-initiated procedures dispatcher
MMProcCnDispatcher = {
@ -1085,3 +1068,6 @@ MMProcCnDispatcher = {
41: MMAbort,
50: MMInformation
}
MMProcCnDispatcherStr = {ProcClass.Cont[0][0]()._name: ProcClass \
for ProcClass in MMProcCnDispatcher.values()}

File diff suppressed because it is too large Load Diff

View File

@ -31,13 +31,13 @@ from .utils import *
from .ProcProto import *
#------------------------------------------------------------------------------#
# RUA signaling procedure
# RUA signalling procedure
# TS 25.468, version d10
# HNB-GW side
#------------------------------------------------------------------------------#
class RUASigProc(LinkSigProc):
"""RUA signaling procedure handler
"""RUA signalling procedure handler
instance attributes:
- Name : procedure name
@ -62,24 +62,33 @@ class RUASigProc(LinkSigProc):
# to store PDU traces
self._pdu = []
# list of PDU to be sent to the HNB
self._snd = []
self._pdu_tx = []
#
self._log('DBG', 'instantiating procedure')
def _log(self, logtype, msg):
self.HNB._log(logtype, '[%s] %s' % (self.Name, msg))
def recv(self, pdu):
def _recv(self, pdu_rx):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self._pdu.append( (time(), 'UL', pdu_rx) )
self.errcause, self.ConInfo = None, {}
try:
self.decode_pdu(pdu_rx, self.ConInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu (%s), sending error indication' % err)
self.errcause = ('protocol', 'abstract-syntax-error-reject')
def recv(self, pdu_rx):
self._recv(pdu_rx)
self._log('ERR', 'recv() not implemented')
def send(self):
if self.TRACK_PDU:
for pdu in self._snd:
for pdu in self._pdu_tx:
self._pdu.append( (time(), 'DL', pdu) )
# send back the list of PDU to be returned to the HNB
return self._snd
return self._pdu_tx
def trigger(self):
self._log('ERR', 'trigger() not implemented')
@ -87,7 +96,10 @@ class RUASigProc(LinkSigProc):
def abort(self):
self._log('INF', 'aborting')
# All following RUA procedures have recv() and trigger() methods defined
# corresponding to HNB-initiated procedures
class RUAConnect(RUASigProc):
"""Connect: TS 25.468, section 8.2
@ -123,23 +135,12 @@ class RUAConnect(RUASigProc):
'uns': None
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
self.errcause, self.retpdu, self.ConInfo = None, None, {}
try:
self.decode_pdu(pdu, self.ConInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
self.errcause = ('protocol', 'abstract-syntax-error-reject')
#
if self.errcause is None:
# inform on EC
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
# log EC request
if self.ConInfo['Establishment_Cause'] == 'emergency-call':
self._log('WNG', 'emergency call requested')
#
# get the UE corresponding to the RUA ctx_id
ctx_id = self.ConInfo['Context_ID'][0]
try:
@ -149,43 +150,33 @@ class RUAConnect(RUASigProc):
% self.ConInfo['Context_ID'][0])
self.errcause = ('radioNetwork', 'connect-failed')
else:
#
# set the Iu context-id and dispatch the RANAP PDU
if self.ConInfo['CN_DomainIndicator'] == 'cs-domain':
self.HNB.set_ue_iucs(ued, ctx_id)
ued.IuCS.set_ran(self.HNB)
ued.IuCS.set_ctx(ctx_id)
try:
self.retpdu = ued.IuCS.process_ranap(self.ConInfo['RANAP_Message'])
except Exception as err:
self._log('ERR', 'RANAP processing failed')
self._err = err
self.retpdu = []
self.retpdu = ued.IuCS.process_ranap(self.ConInfo['RANAP_Message'])
else:
# self.ConInfo['CN_DomainIndicator'] == 'ps-domain', no other choice
#self.ConInfo['CN_DomainIndicator'] == 'ps-domain'
self.HNB.set_ue_iups(ued, ctx_id)
ued.IuPS.set_ran(self.HNB)
ued.IuPS.set_ctx(ctx_id)
try:
self.retpdu = ued.IuPS.process_ranap(self.ConInfo['RANAP_Message'])
except Exception as err:
self._log('ERR', 'RANAP processing failed')
self._err = err
self.retpdu = []
self.retpdu = ued.IuPS.process_ranap(self.ConInfo['RANAP_Message'])
def trigger(self):
if self.errcause is not None:
Err = RUAErrorInd(self.HNB)
Err.encode_pdu('ini', Cause=self.errcause)
if self.errcause:
Err = self.HNB.init_rua_proc(RUAErrorInd, Cause=self.errcause)
return [Err]
else:
RetProc = []
Trans = []
for pdu in self.retpdu:
# wrap each RANAP PDU into a RUA direct transfer PDU
RetProc.append( RUADirectTransfer(self.HNB) )
RetProc[-1].encode_pdu('ini', Context_ID=self.ConInfo['Context_ID'],
RANAP_Message=pdu,
CN_DomainIndicator=self.ConInfo['CN_DomainIndicator'])
return RetProc
Trans.append( self.HNB.init_rua_proc(RUADirectTransfer,
Context_ID=self.ConInfo['Context_ID'],
RANAP_Message=pdu,
CN_DomainIndicator=self.ConInfo['CN_DomainIndicator']) )
return Trans
class RUADirectTransfer(RUASigProc):
@ -220,19 +211,9 @@ class RUADirectTransfer(RUASigProc):
'uns': None
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
self.errcause, self.retpdu, self.ConInfo = None, None, {}
try:
self.decode_pdu(pdu, self.ConInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
self.errcause = ('protocol', 'abstract-syntax-error-reject')
#
if self.errcause is None:
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
# get the UE corresponding to the ctx_id and dispatch the RANAP PDU
if self.ConInfo['CN_DomainIndicator'] == 'cs-domain':
try:
@ -242,12 +223,7 @@ class RUADirectTransfer(RUASigProc):
% self.ConInfo['Context_ID'][0])
self.errcause = ('protocol', 'abstract-syntax-error-reject')
else:
try:
self.retpdu = ued.IuCS.process_ranap(self.ConInfo['RANAP_Message'])
except Exception as err:
self._log('ERR', 'RANAP processing failed')
self._err = err
self.retpdu = []
self.retpdu = ued.IuCS.process_ranap(self.ConInfo['RANAP_Message'])
else:
try:
ued = self.HNB.UE_IuPS[self.ConInfo['Context_ID'][0]]
@ -256,27 +232,21 @@ class RUADirectTransfer(RUASigProc):
% self.ConInfo['Context_ID'][0])
self.errcause = ('protocol', 'abstract-syntax-error-reject')
else:
try:
self.retpdu = ued.IuPS.process_ranap(self.ConInfo['RANAP_Message'])
except Exception as err:
self._log('ERR', 'RANAP processing failed')
self._err = err
self.retpdu = []
self.retpdu = ued.IuPS.process_ranap(self.ConInfo['RANAP_Message'])
def trigger(self):
if self.errcause is not None:
Err = RUAErrorInd(self.HNB)
Err.encode_pdu('ini', Cause=self.errcause)
if self.errcause:
Err = self.HNB.init_rua_proc(RUAErrorInd, Cause=self.errcause)
return [Err]
else:
RetProc = []
Trans = []
for pdu in self.retpdu:
# wrap each RANAP PDU into a RUA direct transfer PDU
RetProc.append( RUADirectTransfer(self.HNB) )
RetProc[-1].encode_pdu('ini', Context_ID=self.ConInfo['Context_ID'],
RANAP_Message=pdu,
CN_DomainIndicator=self.ConInfo['CN_DomainIndicator'])
return RetProc
Trans.append( self.HNB.init_rua_proc(RUADirectTransfer,
Context_ID=self.ConInfo['Context_ID'],
RANAP_Message=pdu,
CN_DomainIndicator=self.ConInfo['CN_DomainIndicator']) )
return Trans
class RUADisconnect(RUASigProc):
@ -312,20 +282,9 @@ class RUADisconnect(RUASigProc):
'uns': None
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
self.errcause, self.ConInfo = None, {}
try:
self.decode_pdu(pdu, self.ConInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
self.errcause = ('protocol', 'abstract-syntax-error-reject')
#
if self.errcause is None:
#
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
# get the UE corresponding to the ctx_id and dispatch the RANAP PDU
if self.ConInfo['CN_DomainIndicator'] == 'cs-domain':
try:
@ -335,15 +294,10 @@ class RUADisconnect(RUASigProc):
% self.ConInfo['Context_ID'][0])
self.errcause = ('protocol', 'abstract-syntax-error-reject')
else:
try:
self.retpdu = ued.IuCS.process_ranap(self.ConInfo['RANAP_Message'])
except Exception as err:
self._log('ERR', 'RANAP processing failed')
self._err = err
self.retpdu = []
# there should be no RANAP answer from the CN
self.retpdu = ued.IuCS.process_ranap(self.ConInfo['RANAP_Message'])
# RANAP_Message should be an IuRelease response
# hence, there should be no RANAP answer from the CN
# after receiving an RUA disconnect
# i.e. containing a RANAP IuRelease response
assert( self.retpdu == [] )
self.HNB.unset_ue_iucs(self.ConInfo['Context_ID'][0])
else:
@ -354,22 +308,13 @@ class RUADisconnect(RUASigProc):
% self.ConInfo['Context_ID'][0])
self.errcause = ('protocol', 'abstract-syntax-error-reject')
else:
try:
self.retpdu = ued.IuPS.process_ranap(self.ConInfo['RANAP_Message'])
except Exception as err:
self._log('ERR', 'RANAP processing failed')
self._err = err
self.retpdu = []
# there should be no RANAP answer from the CN
# after receiving an RUA disconnect
# i.e. containing a RANAP IuRelease response
self.retpdu = ued.IuPS.process_ranap(self.ConInfo['RANAP_Message'])
assert( self.retpdu == [] )
self.HNB.unset_ue_iups(self.ConInfo['Context_ID'][0])
def trigger(self):
if self.errcause is not None:
Err = RUAErrorInd(self.HNB)
Err.encode_pdu('ini', Cause=self.errcause)
Err = self.HNB.init_rua_proc(RUAErrorInd, Cause=self.errcause)
return [Err]
else:
# nothing to trigger after an RUA disconnect
@ -406,39 +351,23 @@ class RUAConnectlessTransfer(RUASigProc):
'uns': None
}
def recv(self, pdu):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
#
self.errcause, self.retpdu, self.ConInfo = None, None, {}
try:
self.decode_pdu(pdu, self.ConInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
self.errcause = ('protocol', 'abstract-syntax-error-reject')
#
if self.errcause is None:
def recv(self, pdu_rx):
self._recv(pdu_rx)
if not self.errcause:
# connection-less transfer, process the RANAP PDU directly in the RNC handler
try:
self.retpdu = self.HNB.process_ranap(self.ConInfo['RANAP_Message'])
except Exception as err:
self._log('ERR', 'RANAP processing failed')
self._err = err
self.retpdu = []
self.retpdu = self.HNB.process_ranap(self.ConInfo['RANAP_Message'])
def trigger(self):
if self.errcause is not None:
Err = RUAErrorInd(self.HNB)
Err.encode_pdu('ini', Cause=self.errcause)
Err = self.HNB.init_rua_proc(RUAErrorInd, Cause=self.errcause)
return [Err]
else:
RetProc = []
Trans = []
for pdu in self.retpdu:
# wrap each RANAP PDU into a RUA direct transfer PDU
RetProc.append( RUAConnectlessTransfer(self.HNB) )
RetProc[-1].encode_pdu('ini', RANAP_Message=pdu)
return RetProc
# wrap each RANAP PDU into a RUA connect less transfer PDU
Trans.append( self.HNB.init_rua_proc(RUAConnectlessTransfer,
RANAP_Message=pdu) )
return Trans
class RUAErrorInd(RUASigProc):
@ -472,36 +401,32 @@ class RUAErrorInd(RUASigProc):
'uns': None
}
def recv(self, pdu):
# we don't respond to a malformed error ind with another error ind
errcause = None
def recv(self, pdu_rx):
if self.TRACK_PDU:
self._pdu.append( (time(), 'UL', pdu) )
self._pdu.append( (time(), 'UL', pdu_rx) )
#
# WNG: there is no distinction between HNB / GW-initiated error ind
# procedure
# WNG: this is the same error ind procedure used for
# HNB-initiated and GW-initiated procedure
#
if pdu[0] == 'initiatingMessage' and pdu[1]['procedureCode'] == 1:
# error indication sent by the HNB: meaning the HNB failed to process
# our previous msg sent to it
# all RUA procedures being initiatingMessage-only, there is nothing
# to abort
if pdu_rx[0] == 'initiatingMessage' and pdu_rx[1]['procedureCode'] == 1:
# HNB-initiated
self.ErrInfo = {}
try:
self.decode_pdu(pdu, self.ErrInfo)
self.decode_pdu(pdu_rx, self.ErrInfo)
except Exception as err:
self._err = err
self._log('ERR', 'decode_pdu: %s' % err)
# do not respond to an error ind, with another error ind...
# don't respond with another error ind
else:
self._log('WNG', 'error indication received: %s.%s' % self.ErrInfo['Cause'])
self._log('WNG', 'error ind received: %s.%s' % self.ErrInfo['Cause'])
#
else:
# any message sent by the HNB, for which we are going to sent back an error ind
# this is handled in the HNBHdlr or in other RUASigProc
pass
# otherwise: GW-initiated, nothing to do
def trigger(self):
# nothing to trigger after an RUA Error
# especially, we don't report Error Ind on malformed received Error Ind
# nothing to trigger after an error ind
return []
@ -533,6 +458,7 @@ class RUAPrivateMessage(RUASigProc):
}
# initializing all RUA procedures classes
RUAConnect.init()
RUADirectTransfer.init()
RUADisconnect.init()
@ -549,3 +475,4 @@ RUAProcDispatcher = {
5 : RUAErrorInd,
6 : RUAPrivateMessage
}

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@ from .utils import *
class LinkSigProc(SigProc):
"""wrapping class that defines common methods for Iu-based and S1-based
signaling procedures; relies heavily on the ASN.1 definitions
signalling procedures; relies heavily on the ASN.1 definitions
"""
# to keep track of the PDU(s) exchanged within this procedure
@ -52,7 +52,7 @@ class LinkSigProc(SigProc):
# default criticality for encoding ASN.1 undefined IE / Ext
_criticality_undef = 'ignore'
# ASN.1 procedure description (HNBAP.HNBAP_PDU_Descriptions.*)
# ASN.1 procedure description (e.g. HNBAP.HNBAP_PDU_Descriptions.*)
Desc = None
# Custom decoders:
@ -86,8 +86,10 @@ class LinkSigProc(SigProc):
"""
# 1) get procedure code and criticality from description
desc = cls.Desc()
cls.Code = desc['procedureCode']
cls.Crit = desc['criticality']
cls.Code = desc['procedureCode']
cls.Crit = desc['criticality']
# class 1: request-response, class 2: request only
cls.Class = 2
# 2) retrieve PDU(s) content from description
# -> get dict of protocolIEs (ident: value's type)
@ -101,6 +103,9 @@ class LinkSigProc(SigProc):
('Outcome', 'suc'), # this is used in RANAP
('UnsuccessfulOutcome', 'uns')):
if ptype[0] in desc:
if ptype[1] != 'ini':
# request-response procedure
cls.Class = 1
encod, decod = cls.Encod[ptype[1]], cls.Decod[ptype[1]]
content, cont_ies, cont_exts, mand = desc[ptype[0]], {}, {}, []
if 'protocolIEs' in content._cont:
@ -247,7 +252,7 @@ class LinkSigProc(SigProc):
def encode_pdu(self, ptype, **kw):
"""encode the provided IEs' values from **kw into the PDU of type ptype
('ini', 'suc' or 'uns') and stack it in self._snd
('ini', 'suc' or 'uns') and stack it in self._pdu_tx
values provided in self.Encod will be set in priority, potentially
overriding those in **kw
@ -361,23 +366,23 @@ class LinkSigProc(SigProc):
val['protocolIEs'] = pdu_ies
if pdu_exts:
val['protocolExtensions'] = pdu_exts
self._snd.append( (self._ptype_lut[ptype],
{'procedureCode': self.Code,
'criticality': self.Crit,
'value': (Cont._tr._name, val)}) )
self._pdu_tx.append( (self._ptype_lut[ptype],
{'procedureCode': self.Code,
'criticality': self.Crit,
'value': (Cont._tr._name, val)}) )
#--------------------------------------------------------------------------#
def recv(self, pdu):
"""process the PDU received by the signaling stack
"""process the PDU received by the signalling stack
"""
self._log('ERR', 'recv() not implemented')
def send(self):
"""return a list of PDU(s) to be sent by the signaling stack
"""return a list of PDU(s) to be sent by the signalling stack
"""
self._log('ERR', 'send() not implemented')
return self._snd
return self._pdu_tx
def trigger(self):
"""return a list of new procedure(s) which were created during previous
@ -393,14 +398,15 @@ class LinkSigProc(SigProc):
#------------------------------------------------------------------------------#
# NAS signaling procedures
# NAS signalling procedures
#------------------------------------------------------------------------------#
_get_first = lambda x: x[1]
_get_second = lambda x: x[2]
tlv_get_first = lambda x: x[1]
tlv_get_second = lambda x: x[2]
tlv_get_cap = lambda x: (x._V.get_val(), x._IE)
class NASSigProc(SigProc):
"""wrapping class that defines common methods for NAS signaling procedures
"""wrapping class that defines common methods for NAS signalling procedures
"""
# to keep track of the NAS message(s) exchanged within this procedure
@ -422,8 +428,14 @@ class NASSigProc(SigProc):
# this allows to override values passed at runtime or set static values
Encod = {}
# NAS message processing filter, built at class init
Filter = []
# NAS message processing filter, by (ProtDisc, Type), built at class init
Filter = set()
# NAS message processing filter, by message name, built at class init
FilterStr = set()
# list of IE (essentially capabilities), for which we want to get the raw
# buffer and the decoded IE value
Cap = ()
#--------------------------------------------------------------------------#
@ -433,10 +445,10 @@ class NASSigProc(SigProc):
NAS message type accepted by the procedure handler, and default .Decod
attribute to extract only V part of LV / TV / TLV IEs.
filter_init = 0, builds a Filter with CN-initiated message
filter_init = 1, builds a Filter with UE-initiated message
filter_init = 0, builds Filter / FilterStr with CN-initiated message
filter_init = 1, builds Filter / FilterStr with UE-initiated message
"""
ContLUT, Encod, Decod, Filter = {}, {}, {}, []
ContLUT, Encod, Decod = {}, {}, {}
#
# CN-initiated NAS msg
if cls.Cont[0] is not None:
@ -456,11 +468,12 @@ class NASSigProc(SigProc):
# build default decoders when not user-defined
for ie in mies:
if ie._name not in Decod[mid]:
if isinstance(ie, (Type1TV, Type3TV, Type4LV, Type6LVE)):
Decod[mid][ie._name] = _get_first
if ie._name in cls.Cap:
Decod[mid][ie._name] = tlv_get_cap
elif isinstance(ie, (Type1TV, Type3TV, Type4LV, Type6LVE)):
Decod[mid][ie._name] = tlv_get_first
elif isinstance(ie, (Type4TLV, Type6TLVE)):
Decod[mid][ie._name] = _get_second
Decod[mid][ie._name] = tlv_get_second
#
# UE-initiated NAS msg
if cls.Cont[1] is not None:
@ -480,17 +493,23 @@ class NASSigProc(SigProc):
# build default decoders when not user-defined
for ie in mies:
if ie._name not in Decod[mid]:
if isinstance(ie, (Type1TV, Type3TV, Type4LV, Type6LVE)):
Decod[mid][ie._name] = _get_first
if ie._name in cls.Cap:
Decod[mid][ie._name] = tlv_get_cap
elif isinstance(ie, (Type1TV, Type3TV, Type4LV, Type6LVE)):
Decod[mid][ie._name] = tlv_get_first
elif isinstance(ie, (Type4TLV, Type6TLVE)):
Decod[mid][ie._name] = _get_second
Decod[mid][ie._name] = tlv_get_second
#
Filter = []
cls.ContLUT, cls.Encod, cls.Decod = ContLUT, Encod, Decod
#
Filter, FilterStr = set(), set()
if cls.Cont[filter_init] is not None:
[Filter.append( (msgclass()['ProtDisc'](), msgclass()['Type']()) )
for msgclass in cls.Cont[filter_init]]
#
cls.ContLUT, cls.Encod, cls.Decod, cls.Filter = ContLUT, Encod, Decod, Filter
for msgclass in cls.Cont[filter_init]:
msg = msgclass()
Filter.add( (msg['ProtDisc'](), msg['Type']()) )
FilterStr.add( msg._name )
if Filter:
cls.Filter, cls.FilterStr = Filter, FilterStr
#--------------------------------------------------------------------------#
@ -607,13 +626,13 @@ class NASSigProc(SigProc):
#--------------------------------------------------------------------------#
def output(self):
"""return a NAS msg to be sent by the signaling stack
"""return a NAS msg to be sent by the signalling stack
"""
self._log('ERR', 'output() not implemented')
return None
def process(self, msg):
"""process the NAS msg received by the signaling stack
"""process the NAS msg received by the signalling stack
"""
self._log('ERR', 'process() not implemented')
return None
@ -628,3 +647,4 @@ class NASSigProc(SigProc):
"""abort the procedure, e.g. due to a timeout or an error indication
"""
pass

View File

@ -38,11 +38,12 @@
# and connects them to specific service handler (SMS, GTPU, ...)
#------------------------------------------------------------------------------#
from .utils import *
from .HdlrHNB import HNBd
from .HdlrENB import ENBd
from .HdlrUE import UEd
from .ServerAuC import AuC
from .utils import *
from .HdlrHNB import HNBd
from .HdlrENB import ENBd
from .HdlrUE import UEd
from .ServerAuC import AuC
from .ServerGTPU import GTPUd
# to log all the SCTP socket send() / recv() calls
@ -50,21 +51,22 @@ DEBUG_SK = False
# global HNB debug level
HNBd.DEBUG = ('ERR', 'WNG', 'INF') #, 'DBG')
HNBd.TRACE_ASN_HNBAP = False
HNBd.TRACE_ASN_RUA = False
HNBd.TRACE_ASN_RANAP = False
HNBd.TRACE_ASN_HNBAP = False
HNBd.TRACE_ASN_RUA = False
HNBd.TRACE_ASN_RANAP = False
# global eNB debug level
ENBd.DEBUG = ('ERR', 'WNG', 'INF') #, 'DBG')
ENBd.TRACE_ASN_S1AP = False
ENBd.DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
ENBd.TRACE_ASN_S1AP = False
# global UE debug level
UEd.DEBUG = ('ERR', 'WNG', 'INF') #, 'DBG')
UEd.TRACE_RANAP_CS = False
UEd.TRACE_RANAP_PS = False
UEd.TRACE_S1AP = False
UEd.TRACE_NAS_CS = False
UEd.TRACE_NAS_PS = False
UEd.TRACE_NAS_EMMENC = False
UEd.TRACE_NAS_EPS = False
UEd.DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
UEd.TRACE_RANAP_CS = False
UEd.TRACE_RANAP_PS = False
UEd.TRACE_NAS_CS = False
UEd.TRACE_NAS_PS = False
UEd.TRACE_S1AP = True
UEd.TRACE_NAS_EPS_ENC = True
UEd.TRACE_NAS_EPS = True
UEd.TRACE_NAS_EPS_SMS = True
class CorenetServer(object):
@ -89,18 +91,17 @@ class CorenetServer(object):
# HNBAP server
SERVER_HNB = {'INET' : socket.AF_INET,
'IP' : '10.1.1.1',
#'IP' : '127.0.1.1',
'port' : 29169,
'MAXCLI': SERVER_MAXCLI,
'errclo': True}
#SERVER_HNB = {} # disabling HNB server
# S1AP server
SERVER_ENB = {'INET' : socket.AF_INET,
'IP' : '127.0.1.1',
'IP' : '127.0.1.100',
'port' : 36412,
'MAXCLI': SERVER_MAXCLI,
'errclo': False}
SERVER_ENB = {} # disabling S1AP server
#SERVER_ENB = {} # disabling S1AP server
#
# Server scheduler resolution:
# This is the timeout on the main select() loop.
@ -120,9 +121,9 @@ class CorenetServer(object):
# Authentication Centre
AUCd = AuC
# GTPU trafic forwarder
GTPUd = None
GTPUd = GTPUd
# SMS center
SMSd = None
SMSd = None
#--------------------------------------------------------------------------#
# corenet global config parameters
@ -130,6 +131,9 @@ class CorenetServer(object):
#
# main PLMN served
PLMN = '20869'
# MME GroupID and Code
MME_GID = 1
MME_CODE = 1
# equivalent PLMNs served
# None or list of PLMNs ['30124', '763326', ...]
EQUIV_PLMN = None
@ -140,7 +144,16 @@ class CorenetServer(object):
EMERG_NUMS = None
#
# S1 connection MME parameters
ConfigS1 = {}
ConfigS1 = {
'MMEName': 'CorenetMME',
'GUMMEIs': [
{'PLMNs': [PLMN], 'GroupIDs': [MME_GID], 'MMECs': [MME_CODE]},
#{'PLMNs': [PLMN] + EQUIV_PLMN, 'GroupIDs': [MME_GID], 'MMECs': [MME_CODE]},
], # this is converted to a ServedGUMMEIs SEQUENCE at runtime
'RelativeMMECapacity': 10,
'EquivPLMNList' : EQUIV_PLMN,
'EmergNumList' : EMERG_NUMS,
}
# HNBAP connection GW parameters
ConfigHNBAP = {}
# RUA connection GW parameters
@ -183,11 +196,30 @@ class CorenetServer(object):
#
# UE configuration parameters
ConfigUE = {
'*': {'IPAddr': (1, '192.168.132.199'), # PDN type (1:IPv4, 2:IPv6, 3:IPv4v6), IP address
'MSISDN': '0123456789', # phone number
'USIM' : True, # Milenage supported
# $IMSI: {'PDN' : [($APN -str-, $PDNType -1..3-, $IPAddr -str-), ...],
# 'MSISDN': $phone_num -str-,
# 'USIM' : $milenage_supported -bool-}
# PDN type: 1:IPv4, 2:IPv6, 3:IPv4v6
'*': {'PDP' : [],
'PDN' : [('*', 1, '192.168.132.199')],
'MSISDN': '0123456789',
'USIM' : True
},
# $IMSI: {IPaddr, MSISDN, USIM, ...}
'208691664001001': {'PDP' : [],
'PDN' : [('*', 1, '192.168.132.201')],
'MSISDN': '16641001',
'USIM' : True
}
}
#
# Packet Data Protocol config for 2G-3G PS domain, per APN
ConfigPDP = {
}
#
# Packet Data Network config for EPC, per APN
ConfigPDN = {
'*' : {'DNS': ('192.168.253.1', '192.168.253.2')},
'corenet': {'DNS': ('192.168.253.1', '192.168.253.2')},
}
#
# UE, indexed by IMSI, and their UEd handler instance
@ -257,6 +289,7 @@ class CorenetServer(object):
self.SCTPServ.append( self._sk_hnb )
else:
self._sk_hnb = None
#
if self.SERVER_ENB:
self._start_enb_server()
self.SCTPServ.append( self._sk_enb )
@ -266,9 +299,14 @@ class CorenetServer(object):
#
# init the dict for storing UE with unknown IMSI at attachment
self._UEpre = {}
# init the UE procedure cleaner holder (with a dummy thread)
# init the UE procedure cleaner holder
# (with a dummy thread, which will be overridden at runtime)
self._clean_ue_proc = threadit( lambda: 1 )
#
self.LAI.clear()
self.RAI.clear()
self.TAI.clear()
#
# start sub-servers
if self.AUCd:
self.AUCd = self.__class__.AUCd()
@ -311,7 +349,6 @@ class CorenetServer(object):
server_addr = (self.SERVER_ENB['IP'], self.SERVER_ENB['port'])
try:
self._sk_enb = sctp.sctpsocket_tcp(self.SERVER_ENB['INET'])
#self._sk_enb.set_adaptation(self.SERVER_ENB['ppid'])
self.sctp_set_events(self._sk_enb)
except Exception as err:
raise(CorenetErr('cannot create SCTP socket: {0}'.format(err)))
@ -354,8 +391,8 @@ class CorenetServer(object):
self.handle_stream_msg(sk)
#
# clean-up potential signalling procedures in timeout
if self.SCHED_UE_TO and not self._clean_ue_proc.isAlive() and \
time() - T0 > self.SCHED_UE_TO:
if self.SCHED_UE_TO and time() - T0 > self.SCHED_UE_TO and \
not self._clean_ue_proc.isAlive():
# select() timeout or more than `SCHED_RES' seconds since
# last timeout
self._clean_ue_proc = threadit(self.clean_ue_proc)
@ -378,18 +415,20 @@ class CorenetServer(object):
#
# stop sub-servers
self.AUCd.stop()
self.GTPUd.stop()
def sctp_handle_notif(self, sk, notif):
self._log('DBG', 'SCTP notification: type %i, flags %i' % (notif.type, notif.flags))
# TODO
def sctp_set_events(self, sk):
# configure the SCTP socket to receive the adaptation layer indication
# in sctp_recv() notification
sk.events.adaptation_layer = True
# configure the SCTP socket to receive adaptation layer and stream id
# indications in sctp_recv() notification
sk.events.data_io = True
sk.events.adaptation_layer = True
#sk.events.association = True
sk.events.flush()
#--------------------------------------------------------------------------#
# SCTP stream handler
#--------------------------------------------------------------------------#
@ -438,11 +477,13 @@ class CorenetServer(object):
if isinstance(cli, HNBd):
self._log('DBG', 'HNB %r closed connection' % (cli.ID,))
# remove from the Server location tables
self._unset_hnb_loc(cli)
if cli.Config:
self._unset_hnb_loc(cli)
elif isinstance(cli, ENBd):
self._log('DBG', 'eNB %r closed connection' % (cli.ID,))
# remove from the Server location tables
self._unset_enb_loc(cli)
if cli.Config:
self._unset_enb_loc(cli)
else:
assert()
# update HNB / ENB state
@ -454,9 +495,9 @@ class CorenetServer(object):
if self.TRACE_SK:
self._log('TRACE_SK_DL', buf)
if ppid:
ppid = socket.htonl(ppid)
if stream:
stream = socket.htonl(stream)
ppid = htonl(ppid)
#if stream:
# stream = htonl(stream)
ret = 0
try:
ret = sk.sctp_send(buf, ppid=ppid, stream=stream)
@ -474,61 +515,94 @@ class CorenetServer(object):
if not buf:
# WNG: it may be required to handle SCTP notifications, at some point...
return
# getting SCTP PPID and HNB handler
ppid, hnbid = socket.ntohl(notif.ppid), self.SCTPCli[sk]
hnb = self.RAN[hnbid]
# getting SCTP ppid, stream id and eNB/HNB handler
ppid, sid, ranid = ntohl(notif.ppid), notif.stream, self.SCTPCli[sk]
ran = self.RAN[ranid]
#
if ppid == SCTP_PPID_HNBAP:
assert( isinstance(ran, HNBd) )
hnb = ran
if not asn_hnbap_acquire():
hnb._log('ERR', 'unable to acquire the HNBAP module')
return
try:
PDU_HNBAP.from_aper(buf)
except:
asn_hnbap_release()
hnb._log('WNG', 'invalid HNBAP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
# send an error ind back
hnb.start_hnbap_proc(RANAPErrorIndCN,
Cause=('protocol', 'transfer-syntax-error'))
return
pdu = PDU_HNBAP()
if hnb.TRACE_ASN_HNBAP:
hnb._log('TRACE_ASN_HNBAP_UL', PDU_HNBAP.to_asn1())
asn_hnbap_release()
ret = hnb.process_hnbap_pdu(pdu)
Err = hnb.init_hnbap_proc(HNBAPErrorIndCN,
Cause=('protocol', 'transfer-syntax-error'))
Err.recv(buf)
pdu_tx = Err.send()
else:
pdu_rx = PDU_HNBAP()
if hnb.TRACE_ASN_HNBAP:
hnb._log('TRACE_ASN_HNBAP_UL', PDU_HNBAP.to_asn1())
asn_hnbap_release()
pdu_tx = hnb.process_hnbap_pdu(pdu_rx)
for pdu in pdu_tx:
self.send_hnbap_pdu(hnb, pdu)
#
elif ppid == SCTP_PPID_RUA:
assert( isinstance(ran, HNBd) )
hnb = ran
if not asn_rua_acquire():
hnb._log('ERR', 'unable to acquire the RUA module')
return
try:
PDU_RUA.from_aper(buf)
except:
asn_rua_release()
self._log('WNG', 'invalid RUA PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
# send an error ind back
hnb.start_rua_proc(RUAErrorInd,
Cause=('protocol', 'transfer-syntax-error'))
Err = hnb.init_rua_proc(RUAErrorInd,
Cause=('protocol', 'transfer-syntax-error'))
Err.recv(buf)
pdu_tx = Err.send()
else:
pdu_rx = PDU_RUA()
if hnb.TRACE_ASN_RUA:
hnb._log('TRACE_ASN_RUA_UL', PDU_HNBAP.to_asn1())
asn_rua_release()
pdu_tx = hnb.process_rua_pdu(pdu_rx)
for pdu in pdu_tx:
self.send_rua_pdu(hnb, pdu)
#
elif ppid == SCTP_PPID_S1AP:
assert( isinstance(ran, ENBd) )
enb = ran
if not asn_s1ap_acquire():
enb._log('ERR', 'unable to acquire the S1AP module')
return
pdu = PDU_RUA()
if hnb.TRACE_ASN_RUA:
hnb._log('TRACE_ASN_RUA_UL', PDU_HNBAP.to_asn1())
asn_rua_release()
ret = hnb.process_rua_pdu(pdu)
try:
PDU_S1AP.from_aper(buf)
except:
asn_s1ap_release()
enb._log('WNG', 'invalid S1AP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
Err = enb.init_hnbap_proc(S1APErrorIndNonUECN,
Cause=('protocol', 'transfer-syntax-error'))
pdu_tx = Err.send()
else:
pdu_rx = PDU_S1AP()
if enb.TRACE_ASN_S1AP:
enb._log('TRACE_ASN_S1AP_UL', PDU_S1AP.to_asn1())
asn_s1ap_release()
if sid == enb.SKSid:
# non-UE-associated signalling
pdu_tx = enb.process_s1ap_pdu(pdu_rx)
else:
# UE-associated signalling
pdu_tx = enb.process_s1ap_ue_pdu(pdu_rx, sid)
for pdu in pdu_tx:
self.send_s1ap_pdu(enb, pdu, sid)
#
else:
self._log('ERR', 'invalid SCTP PPID, %i' % ppid)
if self.SERVER_HNB['errclo']:
self._rem_sk(sk)
return
#
# send available PDU(s) back
if ppid == SCTP_PPID_HNBAP:
for retpdu in ret:
self.send_hnbap_pdu(hnb, retpdu)
else:
for retpdu in ret:
self.send_rua_pdu(hnb, retpdu)
def send_hnbap_pdu(self, hnb, pdu):
if not asn_hnbap_acquire():
@ -537,9 +611,9 @@ class CorenetServer(object):
PDU_HNBAP.set_val(pdu)
if hnb.TRACE_ASN_HNBAP:
hnb._log('TRACE_ASN_HNBAP_DL', PDU_HNBAP.to_asn1())
ret = self._write_sk(hnb.SK, PDU_HNBAP.to_aper(), ppid=SCTP_PPID_HNBAP)
buf = PDU_HNBAP.to_aper()
asn_hnbap_release()
return ret
return self._write_sk(hnb.SK, buf, ppid=SCTP_PPID_HNBAP)
def send_rua_pdu(self, hnb, pdu):
if not asn_rua_acquire():
@ -548,14 +622,75 @@ class CorenetServer(object):
PDU_RUA.set_val(pdu)
if hnb.TRACE_ASN_RUA:
hnb._log('TRACE_ASN_RUA_DL', PDU_RUA.to_asn1())
ret = self._write_sk(hnb.SK, PDU_RUA.to_aper(), ppid=SCTP_PPID_RUA)
buf = PDU_RUA.to_aper()
asn_rua_release()
return ret
return self._write_sk(hnb.SK, buf, ppid=SCTP_PPID_RUA)
def send_s1ap_pdu(self, enb, pdu, sid):
if not asn_s1ap_acquire():
enb._log('ERR', 'unable to acquire the S1AP module')
return
PDU_S1AP.set_val(pdu)
if enb.TRACE_ASN_S1AP:
enb._log('TRACE_ASN_S1AP_DL', PDU_S1AP.to_asn1())
buf = PDU_S1AP.to_aper()
asn_s1ap_release()
return self._write_sk(enb.SK, buf, ppid=SCTP_PPID_S1AP, stream=sid)
#--------------------------------------------------------------------------#
# eNodeB connection
#--------------------------------------------------------------------------#
# TODO
def _parse_s1setup(self, pdu):
if pdu[0] != 'initiatingMessage' or pdu[1]['procedureCode'] != 17:
# not initiating / S1Setup
self._log('WNG', 'invalid S1AP PDU for setting up the eNB S1AP link')
return
pIEs, plmn, cellid = pdu[1]['value'][1], None, None
IEs = pIEs['protocolIEs']
if 'protocolExtensions' in pIEs:
Exts = pIEs['protocolExtensions']
else:
Exts = []
for ie in IEs:
if ie['id'] == 59:
# Global-ENB-ID
globenbid = ie['value'][1]
plmn = globenbid['pLMNidentity']
cellid = globenbid['eNB-ID'][1] # both macro / home eNB-ID are BIT STRING
break
if plmn is None or cellid is None:
self._log('WNG', 'invalid S1AP PDU for setting up the eNB S1AP link: '\
'missing PLMN and CellID')
return
# decode PLMN and CellID
try:
PLMN = plmn_buf_to_str(plmn)
CellID = cellid_bstr_to_str(cellid)
return PLMN, CellID
except:
return None
def _send_s1setuprej(self, sk, sid, cause):
IEs = [{'criticality': 'ignore',
'id': 2, # id-Cause
'value': (('S1AP-IEs', 'Cause'), cause)}]
pdu = ('unsuccessfulOutcome',
{'criticality': 'ignore',
'procedureCode': 17,
'value': (('S1AP-PDU-Contents', 'S1SetupFailure'),
{'protocolIEs' : IEs})})
if not asn_s1ap_acquire():
self._log('ERR', 'unable to acquire the S1AP module')
else:
PDU_S1AP.set_val(pdu)
if ENBd.TRACE_ASN_S1AP:
self._log('TRACE_ASN_S1AP_DL', PDU_S1AP.to_asn1())
self._write_sk(sk, PDU_S1AP.to_aper(), ppid=SCTP_PPID_S1AP, stream=sid)
asn_s1ap_release()
if self.SERVER_ENB['errclo']:
sk.close()
def handle_new_enb(self):
sk, addr = self._sk_enb.accept()
@ -565,7 +700,14 @@ class CorenetServer(object):
if not buf:
# WNG: maybe required to handle SCTP notification, at some point
return
# verifying SCTP Payload Protocol ID and setting stream ID for
# non-UE-associated trafic
ppid, sid = ntohl(notif.ppid), notif.stream
if ppid != SCTP_PPID_S1AP:
self._log('ERR', 'invalid S1AP PPID, %i' % ppid)
if self.SERVER_ENB['errclo']:
sk.close()
return
#
if not asn_s1ap_acquire():
self._log('ERR', 'unable to acquire the S1AP module')
@ -577,32 +719,96 @@ class CorenetServer(object):
% hexlify(buf).decode('ascii'))
# return nothing, no need to bother
return
if HNBd.TRACE_ASN_S1AP:
if ENBd.TRACE_ASN_S1AP:
self._log('TRACE_ASN_S1AP_UL', PDU_S1AP.to_asn1())
pdu = PDU_S1AP()
pdu_rx = PDU_S1AP()
asn_s1ap_release()
#
ENBId = self._parse_s1setup(pdu_rx)
if ENBId is None:
# send S1SetupReject
self._send_s1setuprej(sk, cause=('protocol', 'abstract-syntax-error-reject'))
return
elif ENBId not in self.RAN:
if not self.RAN_CONNECT_ANY:
self._log('ERR', 'eNB %r not allowed to connect' % (ENBId, ))
# send S1SetupReject
self._send_s1setuprej(sk, cause=('radioNetwork', 'unspecified'))
return
elif ENBId[0] not in self.RAN_ALLOWED_PLMN:
self._log('ERR', 'eNB %r not allowed to connect, bad PLMN' % (ENBId, ))
self._send_s1setuprej(sk, cause=('radioNetwork', 'unspecified'))
return
else:
# creating an entry for this eNB
enb = ENBd(self, sk, sid)
self.RAN[ENBId] = enb
else:
if self.RAN[ENBId] is None:
# eNB allowed, but not yet connected
enb = ENBd(self, sk, sid)
self.RAN[ENBId] = enb
elif not self.RAN[ENBId].is_connected():
# eNB already connected and disconnected in the past
enb = self.RAN[ENBId]
enb.__init__(self, sk, sid)
else:
# eNB already connected
self._log('ERR', 'eNB %r already connected from address %r'\
% (ENBId, self.RAN[ENBId].SK.getpeername()))
if self.SERVER_ENB['errclo']:
sk.close()
return
#
# process the initial PDU
pdu_tx = enb.process_s1ap_pdu(pdu_rx)
# keep track of the client
self.SCTPCli[sk] = ENBId
# add the enb TAI to the Server location tables
if enb.Config:
self._set_enb_loc(enb)
#
# send available PDU(s) back
if not asn_s1ap_acquire():
enb._log('ERR', 'unable to acquire the S1AP module')
return
for pdu in pdu_tx:
PDU_S1AP.set_val(pdu)
if ENBd.TRACE_ASN_S1AP:
enb._log('TRACE_ASN_S1AP_DL', PDU_S1AP.to_asn1())
self._write_sk(sk, PDU_S1AP.to_aper(), ppid=SCTP_PPID_S1AP, stream=sid)
asn_s1ap_release()
# to be completed
def _set_enb_loc(self, enb):
pass
for tai in enb.Config['TAIs']:
if tai in self.TAI:
assert( enb.ID not in self.TAI[tai] )
self.TAI[tai].add( enb.ID )
else:
self.TAI[tai] = set( (enb.ID, ) )
def _unset_enb_loc(self, enb):
pass
for tai in enb.Config['TAIs']:
try:
self.TAI[tai].remove(enb.ID)
except:
self._log('ERR', 'ENB not referenced into the TAI table')
#--------------------------------------------------------------------------#
# Home-NodeB connection
#--------------------------------------------------------------------------#
def _parse_hnbregreq(self, pdu):
if pdu[0] != 'initiatingMessage':
if pdu[0] != 'initiatingMessage' or pdu[1]['procedureCode'] != 1:
# not initiating / HNBRegisterRequest
self._log('WNG', 'invalid HNBAP PDU for registering the HNB')
return
if pdu[1]['procedureCode'] != 1:
# not HNBRegisterRequest
self._log('WNG', 'invalid HNBAP PDU for registering the HNB')
return
IEs, Exts = pdu[1]['value'][1]['protocolIEs'], pdu[1]['value'][1]['protocolExtensions']
plmn, cellid = None, None
pIEs, plmn, cellid = pdu[1]['value'][1], None, None
IEs = pIEs['protocolIEs']
if 'protocolExtensions' in pIEs:
Exts = pIEs['protocolExtensions']
else:
Exts = []
for ie in IEs:
if ie['id'] == 9:
plmn = ie['value'][1]
@ -636,7 +842,7 @@ class CorenetServer(object):
PDU_HNBAP.set_val(pdu)
if HNBd.TRACE_ASN_HNBAP:
self._log('TRACE_ASN_HNBAP_DL', PDU_HNBAP.to_asn1())
void = self._write_sk(sk, PDU_HNBAP.to_aper(), ppid=SCTP_PPID_HNBAP)
self._write_sk(sk, PDU_HNBAP.to_aper(), ppid=SCTP_PPID_HNBAP)
asn_hnbap_release()
if self.SERVER_HNB['errclo']:
sk.close()
@ -650,7 +856,7 @@ class CorenetServer(object):
# WNG: maybe required to handle SCTP notification, at some point
return
# verifying SCTP Payload Protocol ID
ppid = socket.ntohl(notif.ppid)
ppid = ntohl(notif.ppid)
if ppid != SCTP_PPID_HNBAP:
self._log('ERR', 'invalid HNBAP PPID, %i' % ppid)
if self.SERVER_HNB['errclo']:
@ -714,7 +920,8 @@ class CorenetServer(object):
# keep track of the client
self.SCTPCli[sk] = HNBId
# add the hnb LAI / RAI to the Server location tables
self._set_hnb_loc(hnb)
if hnb.Config:
self._set_hnb_loc(hnb)
#
# send available PDU(s) back
if not asn_hnbap_acquire():
@ -724,7 +931,7 @@ class CorenetServer(object):
PDU_HNBAP.set_val(retpdu)
if HNBd.TRACE_ASN_HNBAP:
hnb._log('TRACE_ASN_HNBAP_DL', PDU_HNBAP.to_asn1())
void = self._write_sk(sk, PDU_HNBAP.to_aper(), ppid=SCTP_PPID_HNBAP)
self._write_sk(sk, PDU_HNBAP.to_aper(), ppid=SCTP_PPID_HNBAP)
asn_hnbap_release()
def _set_hnb_loc(self, hnb):
@ -760,7 +967,7 @@ class CorenetServer(object):
def get_ued(self, **kw):
"""return a UEd instance or None, according to the UE identity provided
kw: imsi (digit-str), tmsi (uint32) or ptmsi (uint32)
kw: imsi (digit-str), tmsi (uint32), ptmsi (uint32) or mtmsi (uint32)
If an imsi is provided, returns the UEd instance in case the IMSI is allowed
If a tmsi or ptmsi is provided, returns
@ -787,20 +994,29 @@ class CorenetServer(object):
return self.UE[self.TMSI[tmsi]]
else:
# creating a UEd instance which will request IMSI
ued = UEd(self, '', tmsi=tmsi)
self._UEpre[tmsi] = ued
return ued
return self.create_dummy_ue(tmsi=tmsi)
elif 'ptmsi' in kw:
ptmsi = kw['ptmsi']
if ptmsi in self.PTMSI:
return self.UE[self.PTMSI[ptmsi]]
else:
# creating a UEd instance which will request IMSI
ued = UEd(self, '', ptmsi=ptmsi)
self._UEpre[ptmsi] = ued
return ued
return self.create_dummy_ue(ptmsi=ptmsi)
elif 'mtmsi' in kw:
mtmsi = kw['mtmsi']
if mtmsi in self.MTMSI:
return self.UE[self.MTMSI[mtmsi]]
else:
# creating a UEd instance which will request IMSI
return self.create_dummy_ue(mtmsi=mtmsi)
return None
def create_dummy_ue(self, **kw):
assert( len(kw) == 1 )
ued = UEd(self, '', **kw)
self._UEpre[tuple(kw.values())[0]] = ued
return ued
def is_imsi_allowed(self, imsi):
if imsi in self.ConfigUE:
# preconfigured UE
@ -871,3 +1087,4 @@ class CorenetServer(object):
#if ue.S1.ESM.Proc:
# pass

View File

@ -111,8 +111,6 @@ class AuC:
SQN : unsigned integer
OP : subscriber specific OP, distinct from self.OP, optional field
"""
self._log('DBG', 'AuC starting')
self.db = {}
try:
# get 3G authentication database AuC.db
@ -137,9 +135,11 @@ class AuC:
self._log('ERR', 'unable to read AuC.db, path: %s' % self.AUC_DB_PATH)
raise(err)
self._save_required = False
#
# initiatlize the Milenage algo with the AuC-defined OP
self.Milenage = Milenage(self.OP)
#
self._log('DBG', 'AuC started')
def _log(self, logtype='DBG', msg=''):
if logtype in self.DEBUG:

View File

@ -0,0 +1,959 @@
# * coding: UTF8 *
#/**
# * Software Name : libmich
# * Version : 0.3.0
# *
# * Copyright © 2013. Benoit Michau. ANSSI.
# *
# * This program is free software: you can redistribute it and/or modify
# * it under the terms of the GNU General Public License version 2 as published
# * by the Free Software Foundation.
# *
# * 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 General Public License for more details.
# *
# * You will find a copy of the terms and conditions of the GNU General Public
# * License version 2 in the "license.txt" file or
# * see http://www.gnu.org/licenses/ or write to the Free Software Foundation,
# * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
# *
# *--------------------------------------------------------
# * File Name : pycrate_corenet/ServerGTPU.py
# * Created : 2013-11-04
# * Authors : Benoit Michau
# *--------------------------------------------------------
#*/
'''
HOWTO:
1) in order to use this GTP tunnels handler, the following parameters need to be configured:
-> some internal parameters
ARPd.GGSN_ETH_IF = 'eth0', ethernet interface toward external networks (e.g. Internet)
APRd.GGSN_MAC_ADDR = '08:00:00:01:02:03', MAC address of the ethernet interface toward external networks
APRd.GGSN_IP_ADDR = '192.168.1.100', IP address set to the ethernet interface toward external networks
GTPUd.EXT_IF = 'eth0', same as ARPd.GGSN_ETH_IF
GTPUd.GGSN_MAC_ADDR = '08:00:00:01:02:03', same as ARPd.GGSN_MAC_ADDR
-> some external network parameters (toward e.g. Internet)
APRd.SUBNET_PREFIX = '192.168.1.0/24', subnet prefix of the LAN connecting to external networks
APRd.ROUTER_MAC_ADDR = 'f4:00:00:01:02:03', the LAN router (1st IP hop) MAC address
APRd.ROUTER_IP_ADDR = '192.168.1.1', the LAN router (1st IP hop) IP address
-> some internal network parameters (toward RNC / eNodeB)
GTPUd.INT_IP = '10.1.1.1', IP address exposed on the RAN side
GTPUd.INT_PORT = 2152, GTPU UDP port to be used by RAN equipments
-> some mobiles parameters
APRd.IP_POOL = {'192.168.1.201', '192.168.1.202'}, the pool of IP addresses to be used by our set of mobiles
GTPUd.BLACKHOLING = 0, BLACKHOLE_LAN, BLACKHOLE_WAN or BLACKHOLE_LAN|BLACKHOLE_WAN,
to filter out all the mobile trafic, no trafic at all, or IP packets to external network only
GTPUd.WL_ACTIVE = True or False, to allow specific IP packets to be forwarded to the external network,
bypassing the BLACKHOLING directive
GTPUd.WL_PORTS = [('UDP', 53), ('UDP', 123)], to specify to list of IP protocol / port to allow in case WL_ACTIVE is True
GTPUd.DPI = True or False, to store packet statistics (protocol / port / DNS requests, see the class DPI) in GTPUd.stats
2) To use the GTPUd, you need to be root or have the capability to start raw sockets:
-> launch the demon, and add_mobile() / rem_mobile() to add or remove GTPU tunnel endpoint.
>>> gsn = GTPUd()
-> to start forwarding IP packets between the external interface and the GTP tunnel
if you want to let the GTPUd manage the attribution of TEID_to_rnc (GTPUd.GTP_TEID_EXT = False)
>>> teid_to_ran = gsn.add_mobile(mobile_ip='192.168.1.201', ran_ip='10.1.1.2', teid_from_ran=0x1)
if you want to manage teid_to_rnc by yourself and just provide its value to GTPUd (GTPUd.GTP_TEID_EXT = True)
>>> gsn.add_mobile(self, mobile_ip='192.168.1.201', ran_ip='10.1.1.2', teid_from_rnc=0x1, teid_to_rnc=0x2)
-> to stop forwading IP packets
>>> gsn.rem_mobile(mobile_ip='192.168.1.201')
-> modules that act on GTPU packets can be added to the GTPUd instance, they must be put in the MOD attribute
Two example modules DNSRESP and TCPSYNACK are provided.
>>> gsn.MOD.append( TCPSYNACK )
3) That's all !
'''
# filtering exports
__all__ = ['ARPd', 'GTPUd', 'DPI', 'MOD', 'DNSRESP', 'TCPSYNACK']
import os
import signal
#
if os.name != 'nt':
from fcntl import ioctl
from socket import timeout
from random import _urandom
else:
print('[ERR] ServerGTPU : you\'re not on *nix system. It\'s not going to work:\n'\
'You need PF_PACKET socket')
from .utils import *
from pycrate_core.elt import Envelope
from pycrate_ether.IP import *
#------------------------------------------------------------------------------#
# GTP-U handler works with Linux PF_PACKET RAW socket on the Internet side
# and with standard GTP-U 3GPP protocol on the RNC / eNB side
# RNC / eNB <=== IP/UDP/GTPU/IP_mobile ===> GTPU_handler
# GTPU_handler <=== RawEthernet/IP_mobile ===> Internet
#
# This way, the complete IP interface of a mobile is exposed through
# this Gi interface.
# It requires the GTPmgr to resolve ARP request on behalf of mobiles
# that it handles: this is the role of ARPd
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
# setting / unsetting ethernet IF in promiscuous mode #
#------------------------------------------------------------------------------#
# copied from scapy (scapy/scapy/arch/linux.py)
SIOCGIFINDEX = 0x8933 # name -> if_index mapping
SOL_PACKET = 263
PACKET_MR_PROMISC = 1
PACKET_ADD_MEMBERSHIP = 1
PACKET_DROP_MEMBERSHIP = 2
def get_if(iff, cmd):
"""Ease SIOCGIF* ioctl calls"""
sk = socket.socket()
ifreq = ioctl(sk, cmd, pack('16s16x', iff.encode('utf8')))
sk.close()
return ifreq
def get_if_index(iff):
return int(unpack('I', get_if(iff, SIOCGIFINDEX)[16:20])[0])
def set_promisc(sk, iff, val=1):
mreq = pack('IHH8s', get_if_index(iff), PACKET_MR_PROMISC, 0, b'')
if val:
cmd = PACKET_ADD_MEMBERSHIP
else:
cmd = PACKET_DROP_MEMBERSHIP
sk.setsockopt(SOL_PACKET, cmd, mreq)
#------------------------------------------------------------------------------#
# ARPd #
#------------------------------------------------------------------------------#
class ARPd(object):
'''
ARP resolver
resolves Ethernet / IP address correspondence on behalf of UE connected over
GTP-U.
The method .resolve(ipaddr) returns the MAC address for the requested IP
address.
It runs a background thread too, that answers ARP requests on behalf of
connected mobiles.
When handling mobiles' network interfaces over GTP-U, the following steps
are followed:
- for outgoing packets:
1) for any destination IP outside of our network (e.g. 192.168.1.0/24),
provide the ROUTER_MAC_ADDR directly
2) for local destination IP address in our subnet,
provide the corresponding MAC address after an ARP resolution
- for incoming packets:
we must answer the router's or local hosts' ARP requests
before being able to receive IP packets to be transferred to the mobiles
ARPd:
maintains the ARP_RESOLV_TABLE
listens on the ethernet interface for:
- incoming ARP requests, and answer it for IP addresses from our IP_POOL
- incoming ARP responses (due to the daemon sending ARP requests)
- incoming IP packets (thanks to promiscous mode) to update the ARP_RESOLV_TABLE
with new MAC addresses opportunistically
sends ARP request when needed to be able then to forward IP packets from mobile
'''
#
# verbosity level: list of log types to display when calling
# self._log(logtype, msg)
DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
#
# recv() buffer length
BUFLEN = 2048
# select() timeout and wait period
SELECT_TO = 0.1
SELECT_SLEEP = 0.05
#
# all Gi interface parameters
# Our GGSN ethernet parameters (IF, MAC and IP addresses)
# (and also the MAC address to be used for any mobiles through our GGSN)
GGSN_ETH_IF = 'eth0'
GGSN_MAC_ADDR = '08:00:00:01:02:03'
GGSN_IP_ADDR = '192.168.1.100'
#
# the set of IP address to be used by our mobiles
IP_POOL = {'192.168.1.201', '192.168.1.202', '192.168.1.203'}
#
# network parameters:
# subnet prefix
# WNG: we only handle IPv4 /24 subnet
SUBNET_PREFIX = '192.168.1.0/24'
# and 1st IP router (MAC and IP addresses)
# this is to resolve directly any IP outside our subnet
ROUTER_MAC_ADDR = 'f4:00:00:01:02:03'
ROUTER_IP_ADDR = '192.168.1.1'
#
CATCH_SIGINT = False
def __init__(self, opportunist=False):
#
self.GGSN_MAC_BUF = mac_aton(self.GGSN_MAC_ADDR)
self.GGSN_IP_BUF = inet_aton(self.GGSN_IP_ADDR)
self.ROUTER_MAC_BUF = mac_aton(self.ROUTER_MAC_ADDR)
self.ROUTER_IP_BUF = inet_aton(self.ROUTER_IP_ADDR)
# use an uint32 for the subnet prefix
prefip, prefmask = self.SUBNET_PREFIX.split('/')
pref = unpack('>I', inet_aton(prefip))[0]
self.SUBNET_MASK = (1<<32)-(1<<(32-int(prefmask)))
self.SUBNET_PREFIX = pref & self.SUBNET_MASK
#
# init RAW ethernet socket for ARP
self.sk_arp = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ntohs(0x0806))
self.sk_arp.settimeout(0.1)
#self.sk_arp.setsockopt(SOL_PACKET, SO_RCVBUF, 0)
self.sk_arp.bind((self.GGSN_ETH_IF, 0x0806))
#self.sk_arp.setsockopt(SOL_PACKET, SO_RCVBUF, 2**24)
#
# init RAW ethernet socket for IPv4
self.sk_ip = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ntohs(0x0800))
self.sk_ip.settimeout(0.1)
#self.sk_ip.setsockopt(SOL_PACKET, SO_RCVBUF, 0)
self.sk_ip.bind((self.GGSN_ETH_IF, 0x0800))
#self.sk_ip.setsockopt(SOL_PACKET, SO_RCVBUF, 2**24)
#
# ARP resolution table
self.ARP_RESOLV_TABLE = {
self.ROUTER_IP_ADDR : self.ROUTER_MAC_BUF,
self.GGSN_IP_ADDR : self.GGSN_MAC_BUF,
}
for ip in self.IP_POOL:
self.ARP_RESOLV_TABLE[ip] = self.GGSN_MAC_BUF
#
# interrupt handler
if self.CATCH_SIGINT:
def sigint_handler(signum, frame):
if self.DEBUG > 1:
self._log('INF', 'CTRL+C caught')
self.stop()
signal.signal(signal.SIGINT, sigint_handler)
#
self.set_opportunist(opportunist)
# starting main listening loop in background
self._listening = True
self._listener_t = threadit(self.listen)
self._log('INF', 'ARP resolver started')
#
# .resolve(ip) method is available for ARP resolution by GTPUd
def _log(self, logtype='DBG', msg=''):
# logtype: 'ERR', 'WNG', 'INF', 'DBG'
if logtype in self.DEBUG:
log('[%s] [ARPd] %s' % (logtype, msg))
def set_opportunist(self, state):
if state:
self.sk_list = (self.sk_arp, self.sk_ip)
else:
self.sk_list = (self.sk_arp, )
def stop(self):
if self._listening:
self._listening = False
sleep(self.SELECT_TO * 2)
try:
self.sk_arp.close()
self.sk_ip.close()
except Exception as err:
self._log('ERR', 'socket error: {0}'.format(err))
def listen(self):
# select() until we receive arp or ip packet
while self._listening:
r = []
r = select(self.sk_list, [], [], self.SELECT_TO)[0]
for sk in r:
try:
buf = sk.recvfrom(self.BUFLEN)[0]
except Exception as err:
self._log('ERR', 'external network error (recvfrom): %s' % err)
buf = b''
# dipatch ARP request / IP response
if sk != self.sk_arp:
# sk == self.sk_ip
if len(buf) >= 34 and buf[12:14] == b'\x08\x00':
self._process_ipbuf(buf)
else:
# sk == self.sk_arp
if len(buf) >= 42 and buf[12:14] == b'\x08\x06':
self._process_arpbuf(buf)
#
# if select() timeouts, take a little rest
if len(r) == 0:
sleep(self.SELECT_SLEEP)
self._log('INF', 'ARP resolver stopped')
def _process_arpbuf(self, buf=bytes()):
# this is an ARP request or response:
arpop = ord(buf[21:22])
# 1) check if it requests for one of our IP
if arpop == 1:
ipreq = inet_ntoa(buf[38:42])
if ipreq in self.IP_POOL:
# reply to it with our MAC ADDR
try:
self.sk_arp.sendto(
b''.join((buf[6:12], self.GGSN_MAC_BUF, # Ethernet hdr
b'\x08\x06\0\x01\x08\0\x06\x04\0\x02',
self.GGSN_MAC_BUF, buf[38:42], # ARP sender
buf[6:12], buf[28:32], # ARP target
b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'))
(self.GGSN_ETH_IF, 0x0806))
except Exception as err:
self._log('ERR', 'external network error (sendto) on ARP '\
'response: %s' % err)
else:
self._log('DBG', 'ARP response sent for IP: %s' % ipreq)
# 2) check if it responses something useful for us
elif arpop == 2:
ipres_buf = buf[28:32]
if unpack('>I', ipres_buf)[0] & self.SUBNET_MASK == self.SUBNET_PREFIX:
ipres = inet_ntoa(ipres_buf)
if ipres not in self.ARP_RESOLV_TABLE:
# WNG: no protection (at all) against ARP cache poisoning
self.ARP_RESOLV_TABLE[ipres] = buf[22:28]
self._log('DBG', 'got ARP response for new local IP: %s' % ipres)
def _process_ipbuf(self, buf=b''):
# this is an random IPv4 packet incoming into our interface:
# check if src IP is in our subnet and not already resolved,
# then store the Ethernet MAC address
# this is an opportunistic behaviour and disabled by default
ipsrc_buf = buf[26:30]
if unpack('>I', ipsrc_buf)[0] & self.SUBNET_MASK == self.SUBNET_PREFIX:
ipsrc = inet_ntoa(ipsrc_buf)
if ipsrc not in self.ARP_RESOLV_TABLE:
# WNG: no protection (at all) against ARP cache poisoning
self.ARP_RESOLV_TABLE[ipsrc] = buf[6:12]
self._log('DBG', 'got MAC address from IPv4 packet for new local IP: %s' % ipsrc)
def resolve(self, ip='192.168.1.2'):
# check if already resolved
if ip in self.ARP_RESOLV_TABLE:
return self.ARP_RESOLV_TABLE[ip]
# or outside our local network
ip_buf = inet_aton(ip)
if unpack('>i', ip_buf)[0] & self.SUBNET_MASK != self.SUBNET_PREFIX:
return self.ROUTER_MAC_BUF
# requesting an IP within our local LAN
# starting a resolution for it
else:
try:
self.sk_arp.sendto(
b''.join((self.ROUTER_MAC_BUF, self.GGSN_MAC_BUF, # Ethernet hdr
b'\x08\x06\0\x01\x08\0\x06\x04\0\x01',
self.GGSN_MAC_BUF, self.GGSN_IP_BUF, # ARP sender
b'\0\0\0\0\0\0', inet_aton(ip), # ARP target
b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'))
(self.GGSN_ETH_IF, 0x0806))
except Exception as err:
self._log('ERR', 'external network error (sendto) on ARP request: %s' % err)
else:
self._log('DBG', 'ARP request sent for local IP: %s' % ip)
# wait for the answer
cnt = 0
while ip not in self.ARP_RESOLV_TABLE:
sleep(self.SELECT_SLEEP)
cnt += 1
if cnt >= 3:
break
if cnt < 3:
return self.ARP_RESOLV_TABLE[ip]
else:
return 6*'b\xFF' # LAN broadcast, maybe a bit strong !
#------------------------------------------------------------------------------#
# GTPUd #
#------------------------------------------------------------------------------#
BLACKHOLE_LAN = 0b01
BLACKHOLE_WAN = 0b10
class GTPUd(object):
'''
GTP-U forwarder
bridges Ethernet to GTP-U to handle IPv4 data traffic of connected UE.
This is to be instanciated as a unique handler for all GTP-U tunnels
in the corenet mobile core network.
Then, it is possible to add or remove GTP tunnel endpoints at will,
for each mobile with methods:
.add_mobile(mobile_ip, rnc_ip, teid_from_rnc)
-> returns teid_to_rnc for the given mobile
.rem_mobile(mobile_ip)
-> returns None
When a GTP-U packet arrives on the internal interface,
the IPv4 payload is transferred to the external Gi interface over an Ethernet header.
When an Ethernet packet arrives on the external Gi interface,
the IPv4 payload is transferred to the internal interface over a GTP-U header.
A little traffic statistics feature can be used with the class attribute:
DPI = True
Traffic statistics are then placed into the attribute .stats
It is populated even if GTP-U trafic is not forwarded (see BLACKHOLING)
A blackholing feature is integrated to disable the forwarding of GTP-U packet
to the local LAN (with BLACKHOLE_LAN) and/or the routed WAN (with BLACKHOLE_WAN).
This is done by setting the BLACKHOLING class attribute.
A whitelist feature (TCP/UDP, port) is also integrated.
To activate if, set the class attribute:
WL_ACTIVE = True
Then, only packets for the given protocols / ports are transferred to the Gi,
by looking into the class attribute:
WL_PORTS = [('UDP', 53), ('UDP', 123), ('TCP', 80), ...]
This is bypassing the blackholing feature.
'''
#
# verbosity level: list of log types to display when calling
# self._log(logtype, msg)
DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
#
# packet buffer space (over MTU...)
BUFLEN = 1536
# select loop settings
SELECT_TO = 0.2
#
# Gi interface, with GGSN ethernet IF and mobile IP address
EXT_IF = ARPd.GGSN_ETH_IF
GGSN_MAC_ADDR = ARPd.GGSN_MAC_ADDR
# IPv4 protocol only, to be forwarded
EXT_PROT = 0x0800
#
# internal IP interface, for handling GTP-U packets from RNC / eNB
INT_IP = '127.0.1.100'
INT_PORT = 2152
#
# GTP TEID toward RNC / eNodeBs (for DL traffic)
GTP_TEID = 0
GTP_TEID_MAX = 2**32 - 1
# in case the GTP TEID is assigned by an external entity
GTP_TEID_EXT = True
#
# BLACKHOLING feature
# to enable the whole traffic: 0
# to disable traffic routed to the WAN: BLACKHOLE_WAN
# to disable traffic to the local LAN: BLACKHOLE_LAN
# to disable the whole forwarding of GTP-U packets: BLACKHOLE_LAN | BLACKHOLE_WAN
BLACKHOLING = 0
# traffic that we want to allow, even if BLACKHOLING is activated
WL_ACTIVE = False
WL_PORTS = [('UDP', 53), ('UDP', 123)]
#
# in case we want to generate traffic statistics (then available in .stats)
DPI = True
#
# in case we want to check and drop spoofed IPv4 source address in incoming
# GTP-U packet
DROP_SPOOF = True
#
CATCH_SIGINT = False
def __init__(self):
#
self.GGSN_MAC_BUF = mac_aton(self.GGSN_MAC_ADDR)
#
# 2 dict for handling mobile GTP-U packets transfers:
# key: mobile IPv4 addr (buf)
# value: (ran_ip (asc), teid_from_ran (int), teid_to_ran (int))
self._mobiles_ip = {}
# key: teid_from_ran (int)
# value: mobile IPv4 addr (buf)
self._mobiles_teid = {}
# global TEID to RAN value, to be incremented from here
if not self.GTP_TEID_EXT:
self.GTP_TEID = randint(0, 200000)
#
# initialize the traffic statistics
self.stats = {}
self._prot_dict = {1:'ICMP', 6:'TCP', 17:'UDP'}
# initialize the list of modules that can act on GTP-U payloads
self.MOD = []
#
# create a RAW PF_PACKET socket on the `Internet` side
# python is not convinient to configure dest mac addr
# when using SOCK_DGRAM (or I missed something...),
# so we use SOCK_RAW and build our own ethernet header:
self.sk_ext = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ntohs(self.EXT_PROT))
# configure timeouting and interface binding
self.sk_ext.settimeout(0.001)
#self.sk_ext.setblocking(0)
self.sk_ext.bind((self.EXT_IF, self.EXT_PROT))
# put the interface in promiscuous mode
set_promisc(self.sk_ext, self.EXT_IF, 1)
#
# create an UDP socket on the RNC / eNB side, on port 2152
self.sk_int = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# configure timeout, binding and rebinding on same address
self.sk_int.settimeout(0.001)
#self.sk_int.setblocking(0)
self.sk_int.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sk_int.bind((self.INT_IP, self.INT_PORT))
#
# interrupt handler
if self.CATCH_SIGINT:
def sigint_handler(signum, frame):
if self.DEBUG > 1:
self._log('INF', 'CTRL+C caught')
self.stop()
signal.signal(signal.SIGINT, sigint_handler)
#
# and start listening and transferring packets in background
self.sk_list = (self.sk_ext, self.sk_int)
self._listening = True
self._listener_t = threadit(self.listen)
self._log('INF', 'GTP-U tunnels handler started')
#
# and finally start ARP resolver
self.arpd = ARPd()
def _log(self, logtype='DBG', msg=''):
# logtype: 'ERR', 'WNG', 'INF', 'DBG'
if logtype in self.DEBUG:
log('[%s] [GTPUd] %s' % (logtype, msg))
def init_stats(self, ip):
self.stats[ip] = {
'DNS': [], # IP of DNS servers requested
'NTP': [], # IP of NTP servers requested
'resolved': [], # domain name resolved
'ICMP': [], # ICMP endpoint (IP) contacted
'TCP': [], # TCP endpoint (IP, port) contacted
'UDP': [], # UDP endpoint (IP, port) contacted
'alien': [], # other protocol endpoint contacted
}
def stop(self):
# stop ARP resolver
self.arpd.stop()
# stop local GTPU handler
if self._listening:
self._listening = False
sleep(self.SELECT_TO * 2)
try:
# unset promiscuous mode
set_promisc(self.sk_ext, self.EXT_IF, 0)
# closing sockets
self.sk_int.close()
self.sk_ext.close()
except Exception as err:
self._log('ERR', 'socket error: %s' % err)
def listen(self):
# select() until we receive something on 1 side
while self._listening:
r = select(self.sk_list, [], [], self.SELECT_TO)[0]
# read ext and int sockets until they are empty
for sk in r:
if sk != self.sk_int:
# sk == self.sk_ext
try:
buf = sk.recvfrom(self.BUFLEN)[0]
#except timeout:
# # nothing to read anymore
# buf = b''
except Exception as err:
self._log('ERR', 'external network IF error (recvfrom): %s' % err)
#buf = b''
else:
if len(buf) >= 34 and \
buf[:6] == self.GGSN_MAC_BUF and \
buf[12:14] == b'\x08\0' and \
buf[16:20] in self._mobiles_ip:
# transferring over GTP-U after removing the Ethernet header
self.transfer_to_int(buf[14:])
#threadit(self.transfer_to_int, buf[14:])
else:
# sk == self.int_sk
try:
buf = sk.recv(self.BUFLEN)
#except timeout:
# # nothing to read anymore
# buf = b''
except Exception as err:
self._log('ERR', 'internal network IF error (recv): %s' % err)
#buf = b''
else:
self.transfer_to_ext(buf)
#threadit(self.transfer_to_ext, buf)
#
self._log('INF', 'GTPU handler stopped')
def transfer_to_ext(self, buf):
# if GTP-U TEID in self._mobiles_teid, just forward...
# in this direction, there is no reason to filter
# except to avoid IP spoofing from malicious mobile
# (damned ! Would it be possible ?!?)
#
# extract the GTP header
try:
flags, msgtype, msglen, teid_from_ran = unpack('>BBHI', buf[:8])
except:
self._log('WNG', 'invalid GTP packet from RAN')
return
#
# in case GTP TEID is not correct, drop it
try:
mobile_ip = self._mobiles_teid[teid_from_ran]
except:
self._log('WNG', 'unknown GTP TEID from RAN: 0x%.8x' % teid_from_ran)
return
#
# in case GTP does not contain UP data, drop it
if msgtype != 0xff:
self._log('WNG', 'unsupported GTP type from RAN: 0x%.2x' % msgtype)
return
#
# get the IP packet: use the length in GTPv1 header to cut the buffer
if flags & 0x04:
# GTP header extended
msglen -= 4
ipbuf = buf[-msglen:]
#
# drop dummy IP packets
if len(ipbuf) < 24:
self._log('WNG', 'dummy packet from mobile dropped: %s' % hexlify(ipbuf).decode('ascii'))
return
#
# drop packet other than IPv4
ipver = ord(ipbuf[0:1]) >> 4
if ipver != 4:
self._log('WNG', 'unsupported IPv%i packet from UE' % ipver)
return
#
# drop spoofed IP packet
if self.DROP_SPOOF and ipbuf[12:16] != mobile_ip:
self._log('WNG', 'spoofed IPv4 source address from UE: %s' % inet_ntoa(ipbuf[12:16]))
return
#
ipsrc = inet_ntoa(ipbuf[12:16])
ipdst = inet_ntoa(ipbuf[16:20])
#
# analyze the packet content for statistics
if self.DPI:
self._analyze(ipsrc, ipbuf)
#
# possibly process the UL GTP-U payload within modules
if self.MOD:
try:
for mod in self.MOD:
if mod.TYPE == 0:
ipbuf = mod.handle_ul(ipbuf)
else:
mod.handle_ul(ipbuf)
except Exception as err:
self._log('ERR', 'MOD error: %s' % err)
#
# resolve the dest MAC addr
macdst = self.arpd.resolve(ipdst)
#
# possibly bypass blackholing rule for allowed ports
# check if PROT / PORT is allowed in the whilelist
if self.BLACKHOLING:
if self.WL_ACTIVE:
dst, prot, pay = DPI.get_ip_dst_pay(ipbuf)
# TCP:6, UDP:17
if prot in (6, 17) and pay:
port = DPI.get_port(pay)
if (self._prot_dict[prot], port) in self.WL_PORTS:
self._transfer_to_ext(macdst, ipbuf)
else:
return
else:
return
elif macdst != self.arpd.ROUTER_MAC_BUF:
if self.BLACKHOLING & BLACKHOLE_LAN:
return
else:
self._transfer_to_ext(macdst, ipbuf)
else:
if self.BLACKHOLING & BLACKHOLE_WAN:
return
else:
self._transfer_to_ext(macdst, ipbuf)
else:
self._transfer_to_ext(macdst, ipbuf)
def _transfer_to_ext(self, macdst, ipbuf):
# forward to the external PF_PACKET socket, over the Gi interface
try:
self.ext_sk.sendto(b''.join((macdst, self.GGSN_MAC_BUF, b'\x08\0', ipbuf))
(self.EXT_IF, self.EXT_PROT))
except Exception as err:
self._log('ERR', 'external network IF error (sendto): %s' % err)
#else:
# self._log('DBG', 'buffer transferred from GTPU to RAW')
def transfer_to_int(self, buf):
# from .listen():
# buf length is guaranteed >= 20 and ipdst in self._mobiles_ip
#
if self.MOD:
# possibly process the DL GTP-U payload within modules
try:
for mod in self.MOD:
if mod.TYPE == 0:
buf = mod.handle_dl(buf)
else:
mod.handle_dl(buf)
except Exception as err:
self._log('ERR', 'MOD error: %s' % err)
#
ran_ip, teid_from_ran, teid_to_ran = self._mobiles_ip[buf[16:20]]
# prepend GTP header and forward to the RAN IP
gtphdr = pack('>BBHI', 0x30, 0xff, len(buf), teid_to_ran)
#
try:
ret = self.int_sk.sendto(gtphdr + buf, (ran_ip, self.INT_PORT))
except Exception as err:
self._log('ERR', 'internal network IF error (sendto): %s' % err)
#else:
# self._log('DBG', '%i bytes transferred from RAW to GTPU' % ret)
###
# Now we can add and remove (mobile_IP, TEID_from/to_RAN),
# to configure filters and really start forwading packets over GTP
def add_mobile(self, mobile_ip='192.168.1.201', ran_ip='10.1.1.1',
teid_from_ran=0x1, teid_to_ran=0x1):
try:
ip = inet_aton(mobile_ip)
except Exception as err:
self._log('ERR', 'mobile_ip (%r) has not the correct format: '\
'cannot configure the GTPU handler' % mobile_ip)
return
if not self.GTP_TEID_EXT:
teid_to_ran = self.get_teid_to_ran()
self._mobiles_ip[ip] = (ran_ip, teid_from_ran, teid_to_ran)
self._mobiles_teid[teid_from_ran] = ip
self._log('INF', 'setting GTP tunnel for mobile with IP %s' % mobile_ip)
return teid_to_ran
def rem_mobile(self, mobile_ip='192.168.1.201'):
try:
ip = inet_aton(mobile_ip)
except Exception as err:
self._log('ERR', 'mobile_ip (%r) has not the correct format: '\
'cannot configure the GTPU handler' % mobile_ip)
return
if ip in self._mobiles_ip:
self._log('INF', 'unsetting GTP tunnel for mobile with IP %s' % mobile_ip)
ran_ip, teid_from_ran, teid_to_ran = self._mobiles_ip[ip]
del self._mobiles_ip[ip]
if teid_from_ran in self._mobiles_teid:
del self._mobiles_teid[teid_from_ran]
def get_teid_to_ran(self):
if self.GTP_TEID >= self.GTP_TEID_MAX:
self.GTP_TEID = randint(0, 200000)
self.GTP_TEID += 1
return self.GTP_TEID
def _analyze(self, ipsrc, ipbuf):
#
try:
stats = self.stats[ipsrc]
except:
self.init_stats(ipsrc)
stats = self.stats[ipsrc]
#
dst, prot, pay = DPI.get_ip_dst_pay(ipbuf)
# UDP
if prot == 17 and pay:
port = DPI.get_port(pay)
if (dst, port) not in stats['UDP']:
stats['UDP'].append((dst, port))
# DNS
if port == 53:
if dst not in stats['DNS']:
stats['DNS'].append(dst)
name = DPI.get_dn_req(pay[8:])
if name not in stats['resolved']:
stats['resolved'].append(name)
elif port == 123 and dst not in stats['NTP']:
stats['NTP'].append(dst)
# TCP
elif prot == 6 and pay:
port = DPI.get_port(pay)
if (dst, port) not in stats['TCP']:
stats['TCP'].append((dst, port))
# ICMP
elif prot == 1 and pay and dst not in stats['ICMP']:
stats['ICMP'].append(dst)
# alien
else:
stats['alien'].append(hexlify(ipbuf))
class DPI:
@staticmethod
def get_ip_dst_pay(ipbuf):
# returns a 3-tuple: dst IP, protocol, payload buffer
# get IP header length
l = (ord(ipbuf[0:1]) & 0x0F) * 4
# get dst IP
dst = inet_ntoa(ipbuf[16:20])
# get protocol
prot = ord(ipbuf[9:10])
#
return (dst, prot, ipbuf[l:])
@staticmethod
def get_port(pay):
return unpack('!H', pay[2:4])[0]
@staticmethod
def get_dn_req(req):
# remove fixed DNS header and Type / Class
s = req[12:-4]
n = []
while len(s) > 1:
l = ord(s[0])
n.append( s[1:1+l] )
s = s[1+l:]
return b'.'.join(n)
class MOD(object):
# This is a skeleton for GTP-U payloads specific handler.
# After It gets loaded by the GTPUd instance,
# it acts on each GTP-U payloads (UL and DL)
# In can work actively on GTP-U packets (possibly changing them)
# with TYPE = 0
# or passively (not able to change them), only processing a copy of them,
# with TYPE = 1
TYPE = 0
# reference to the GTPUd instance
GTPUd = None
@classmethod
def _log(self, logtype, msg):
self.GTPUd._log(logtype, '[MOD.%s] %s' % (self.__class__.__name__, msg))
@classmethod
def handle_ul(self, ippuf):
pass
@classmethod
def handle_dl(self, ipbuf):
pass
class DNSRESP(MOD):
'''
This module answers to any DNS request incoming from UE (UL direction)
with a single or random IP address
To be used with GTPUd.BLACKHOLING capability to avoid UE getting real
DNS responses from servers in parallel
'''
TYPE = 1
# compute UDP checksum in DNS response
UDP_CS = True
# in case we want to answer random addresses
RAND = False
# the IPv4 address to answer all requests
IP_RESP = '192.168.1.50'
@classmethod
def handle_ul(self, ipbuf):
# check if we have an UDP/53 request
ip_proto, (udpsrc, udpdst) = ord(ipbuf[9]), unpack('!HH', ipbuf[20:24])
if ip_proto != 17:
# not UDP
return
if udpdst != 53:
# not DNS
return
# build the UDP / DNS response: invert src / dst UDP ports
if self.UDP_CS:
udp = UDP(val={'src':udpdst, 'dst':udpsrc}, hier=1)
else:
udp = UDP(val={'src':udpdst, 'dst':udpsrc, 'cs':0}, hier=1)
# DNS request: transaction id, flags, questions, queries
dnsreq = ipbuf[28:]
transac_id, questions, queries = dnsreq[0:2], \
unpack('!H', dnsreq[4:6])[0], \
dnsreq[12:]
if questions > 1:
# not supported
self._log('WNG', '%i questions, unsupported' % questions)
# DNS response: transaction id, flags, questions, answer RRs,
# author RRs, add RRs, queries, answers, autor nameservers, add records
if self.RAND:
ip_resp = _urandom(4)
else:
ip_resp = inet_aton(self.IP_RESP)
dnsresp = b''.join((transac_id, b'\x81\x80\0\x01\0\x01\0\0\0\0', queries,
b'\xc0\x0c\0\x01\0\x01\0\0\0\x20\0\x04', ip_resp))
# build the IPv4 header: invert src / dst addr
ipsrc, ipdst = inet_ntoa(ipbuf[12:16]), inet_ntoa(ipbuf[16:20])
iphdr = IPv4(val={'src':ipdst, 'dst':ipsrc}, hier=0)
#
pkt = Envelope('p', GEN=(iphdr, udp, Buf('dns', val=dnsresp, hier=2)))
# send back the DNS response
self.GTPUd.transfer_to_int(pkt.to_bytes())
class TCPSYNACK(MOD):
'''
This module answers to TCP SYN request incoming from UE (UL direction) with
a TCP SYN-ACK, enabling to get the 1st TCP data packet from the UE
To be used with GTPUd.BLACKHOLING capability to avoid UE getting SYN-ACK
from real servers in parallel
'''
TYPE = 1
@classmethod
def handle_ul(self, ipbuf):
# check if we have a TCP SYN
ip_proto, ip_pay = ord(ipbuf[9:10]), ipbuf[20:]
if ip_proto != 6:
# not TCP
return
if ip_pay[13:14] != b'\x02':
# not SYN
return
# build the TCP SYN-ACK: invert src / dst ports, seq num (random),
# ack num (SYN seq num + 1)
tcpsrc, tcpdst, seq = unpack('!HHI', ip_pay[:8])
tcp_synack = TCP(val={'seq': randint(1, 4294967295), 'ack': (1+seq)%4294967296,
'src':tcpdst, 'dst':tcpsrc,
'SYN':1, 'ACK':1, 'win':0x1000}, hier=1)
# build the IPv4 header: invert src / dst addr
ipsrc, ipdst = inet_ntoa(ipbuf[12:16]), inet_ntoa(ipbuf[16:20])
iphdr = IPv4(val={'src':ipdst, 'dst':ipsrc}, hier=0)
#
pkt = Envelope('p', GEN=(iphdr, tcp_synack))
# send back the TCP SYN-ACK
self.GTPUd.transfer_to_int(pkt.to_bytes())

View File

@ -27,10 +27,10 @@
# *--------------------------------------------------------
#*/
__all__ = ['utils', 'Server', 'ServerAuC',
__all__ = ['utils', 'Server', 'ServerAuC', 'ServerGTPU',
'HdlrENB', 'HdlrHNB',
'HdlrUE', 'HdlrUEIu', 'HdlrUEIuCS', 'HdlrUEIuPS', 'HdlrUES1',
'ProcProto', 'ProcCNHnbap', 'ProcCNRua', 'ProcCNRanap',
'ProcCNMM', 'ProcCNGMM',
'HdlrUE', 'HdlrUEIu', 'HdlrUEIuCS', 'HdlrUEIuPS', 'HdlrUES1', 'HdlrUESMS',
'ProcProto', 'ProcCNHnbap', 'ProcCNRua', 'ProcCNRanap', 'ProcCNS1ap',
'ProcCNMM', 'ProcCNGMM', 'ProcCNEMM', 'ProcCNESM',
]
__version__ = '0.2.0'

View File

@ -35,11 +35,12 @@ import re
#import traceback
from select import select
from threading import Thread, Lock, Event
from random import SystemRandom
from random import SystemRandom, randint
from time import time, sleep
from datetime import datetime
from binascii import hexlify, unhexlify
from struct import unpack
from struct import pack, unpack
from socket import ntohl, htonl, ntohs, htons, inet_aton, inet_ntoa
# SCTP support for S1AP / RUA interfaces
try:
@ -58,6 +59,10 @@ except ImportError as err:
raise(err)
from pycrate_core.utils import *
from pycrate_core.repr import *
from pycrate_core.elt import Element
Element._SAFE_STAT = True
Element._SAFE_DYN = True
log('CorenetServer: loading all ASN.1 and NAS modules, be patient...')
# import ASN.1 modules
@ -82,11 +87,12 @@ from pycrate_mobile import TS24080_SS
# PS domain
from pycrate_mobile import TS24008_GMM
from pycrate_mobile import TS24008_SM
#
# to drive LTE UE
from pycrate_mobile import TS24301_EMM
from pycrate_mobile import TS24301_ESM
#
from pycrate_mobile import TS24007
from pycrate_mobile import NAS
@ -230,6 +236,7 @@ def threadit(f, *args, **kwargs):
#------------------------------------------------------------------------------#
# radio access techno identifiers
RAT_GERA = 'GERAN'
RAT_UTRA = 'UTRAN'
RAT_EUTRA = 'E-UTRAN'
@ -273,7 +280,7 @@ def cplist(l):
#------------------------------------------------------------------------------#
# ASN.1 object handling facilities
# various routines
#------------------------------------------------------------------------------#
def pythonize_name(name):
@ -301,9 +308,34 @@ def imsi_str_to_buf(s):
def cellid_bstr_to_str(bstr):
return hexlify(int_to_bytes(*bstr)).decode('ascii')
# 20 or 28 bits
return hexlify(int_to_bytes(*bstr)).decode('ascii')[:-1]
def globenbid_to_hum(seq):
return {'pLMNidentity': plmn_buf_to_str(seq['pLMNidentity']),
'eNB-ID': (seq['eNB-ID'][0], cellid_bstr_to_str(seq['eNB-ID'][1]))}
def supptas_to_hum(seqof):
return [{'broadcastPLMNs': [plmn_buf_to_str(plmn) for plmn in sta['broadcastPLMNs']],
'tAC': bytes_to_uint(sta['tAC'], 16)} for sta in seqof]
def gummei_to_asn(val):
return {'servedGroupIDs': [uint_to_bytes(gid, 16) for gid in val['GroupIDs']],
'servedMMECs': [uint_to_bytes(mmec, 8) for mmec in val['MMECs']],
'servedPLMNs': [plmn_str_to_buf(plmn) for plmn in val['PLMNs']]}
def mac_aton(mac='00:00:00:00:00:00'):
return unhexlify(mac.replace(':', ''))
#------------------------------------------------------------------------------#
# ASN.1 object handling facilities
#------------------------------------------------------------------------------#
def print_pduies(desc):
for ptype in ('InitiatingMessage', 'Outcome', 'SuccessfulOutcome', 'UnsuccessfulOutcome'):
if ptype in desc():
@ -404,3 +436,4 @@ class SigProc(object):
# See ProcProto.py for prototype classes for the various mobile network procedures
# and other Proc*.py for the procedures themselves