corenet: bringing LTE support and improving 3G support
This commit is contained in:
parent
5b84e8280f
commit
9b0b853e49
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -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()}
|
||||
|
|
@ -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()}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,959 @@
|
|||
# −*− coding: UTF−8 −*−
|
||||
#/**
|
||||
# * 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())
|
||||
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue