691 lines
26 KiB
Python
691 lines
26 KiB
Python
# -*- coding: UTF-8 -*-
|
|
#/**
|
|
# * Software Name : pycrate
|
|
# * Version : 0.4
|
|
# *
|
|
# * Copyright 2017. Benoit Michau. ANSSI.
|
|
# *
|
|
# * This library is free software; you can redistribute it and/or
|
|
# * modify it under the terms of the GNU Lesser General Public
|
|
# * License as published by the Free Software Foundation; either
|
|
# * version 2.1 of the License, or (at your option) any later version.
|
|
# *
|
|
# * This library 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
|
|
# * Lesser General Public License for more details.
|
|
# *
|
|
# * You should have received a copy of the GNU Lesser General Public
|
|
# * License along with this library; if not, write to the Free Software
|
|
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
# * MA 02110-1301 USA
|
|
# *
|
|
# *--------------------------------------------------------
|
|
# * File Name : pycrate_corenet/HdlrUEIu.py
|
|
# * Created : 2017-07-11
|
|
# * Authors : Benoit Michau
|
|
# *--------------------------------------------------------
|
|
#*/
|
|
|
|
from .utils import *
|
|
from .ProcCNRua import *
|
|
from .ProcCNRanap import *
|
|
|
|
|
|
# WNG: all procedures that call .require_smc() method need to be set in this LUT
|
|
ProcAbbrLUT = {
|
|
'MMLocationUpdating' : 'LU',
|
|
'MMConnectionEstablishment': 'CON',
|
|
'RRPagingResponse' : 'PAG',
|
|
'GMMAttach' : 'ATT',
|
|
'GMMRoutingAreaUpdating' : 'RAU',
|
|
'GMMServiceRequest' : 'SER',
|
|
}
|
|
|
|
|
|
class UEIuSigStack(SigStack):
|
|
|
|
# to keep track of all RANAP procedures
|
|
TRACK_PROC = True
|
|
|
|
# reference to the UEd
|
|
UE = None
|
|
# reference to the RNCd / HNBd
|
|
RNC = None
|
|
|
|
# core network domain (CS or PS)
|
|
DOM = None
|
|
|
|
# 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, rncd, ctx_id):
|
|
self.UE = ued
|
|
self.Server = ued.Server
|
|
if self.DOM == 'PS':
|
|
self._cndomind = 'ps-domain'
|
|
else:
|
|
self._cndomind = 'cs-domain'
|
|
#
|
|
# dict of ongoing RANAP procedures (indexed by their procedure code)
|
|
self.Proc = {}
|
|
self.ProcLast = None
|
|
# list of tracked procedures (requires TRACK_PROC = True)
|
|
self._proc = []
|
|
#
|
|
# 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 rncd is not None:
|
|
self.set_ran(rncd)
|
|
self.set_ctx(ctx_id)
|
|
else:
|
|
self.unset_ctx()
|
|
|
|
def set_ran(self, rncd):
|
|
# TODO: handle mobility from 1 RNC to another, and inter-RAT
|
|
self.SEC['CKSN'] = None
|
|
self.RNC = rncd
|
|
self.connected.set()
|
|
|
|
def unset_ran(self):
|
|
del self.RNC
|
|
self.SEC['CKSN'] = None
|
|
self.clear()
|
|
self.connected.clear()
|
|
|
|
def set_ran_unconnected(self, rncd):
|
|
# required for paging
|
|
self.SEC['CKSN'] = None
|
|
self.RNC = rncd
|
|
|
|
def unset_ran_unconnected(self):
|
|
# required for paging
|
|
del self.RNC
|
|
self.SEC['CKSN'] = None
|
|
|
|
def is_connected(self):
|
|
#return self.RNC is not None
|
|
return self.connected.is_set()
|
|
|
|
def set_ctx(self, ctx_id):
|
|
self.CtxId = ctx_id
|
|
|
|
def unset_ctx(self):
|
|
self.CtxId = -1
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# 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 ret
|
|
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:
|
|
if self.DOM == 'CS' and self.UE.TRACE_ASN_RANAP_CS:
|
|
self._log('TRACE_ASN_RANAP_CS_DL', '\n' + PDU_RANAP.to_asn1())
|
|
elif self.DOM == 'PS' and self.UE.TRACE_ASN_RANAP_PS:
|
|
self._log('TRACE_ASN_RANAP_PS_DL', '\n' + PDU_RANAP.to_asn1())
|
|
ret.append( PDU_RANAP.to_aper() )
|
|
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 Exception:
|
|
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 Exception:
|
|
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 Exception:
|
|
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' for a connected UE,
|
|
encode the initiatingMessage PDU with given **kw and return the procedure
|
|
"""
|
|
if not issubclass(ProcClass, RANAPSigProc):
|
|
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' % ProcClass.__name__)
|
|
return None
|
|
try:
|
|
Proc = ProcClass(self)
|
|
except Exception:
|
|
# no active Iu link
|
|
self._log('ERR', 'no active Iu link to initialize the RANAP procedure %s'\
|
|
% ProcClass.__name__)
|
|
return None
|
|
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:
|
|
self._proc.append( Proc )
|
|
Proc.encode_pdu('ini', **kw)
|
|
return Proc
|
|
|
|
def clear(self):
|
|
# clears all running RANAP CS/PS procedures
|
|
for Proc in list(self.Proc.values()):
|
|
Proc.abort()
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# SMC and security-related methods
|
|
#--------------------------------------------------------------------------#
|
|
|
|
def require_smc(self, Proc):
|
|
# check if a RANAPSecurityModeControl procedure is required
|
|
if self.SEC_DISABLED or self.SMC_DISABLED:
|
|
return False
|
|
#
|
|
elif ProcAbbrLUT[Proc.Name] in self.SMC_DISABLED_PROC:
|
|
return False
|
|
#
|
|
elif self.SEC['CKSN'] is None or self.SEC['CKSN'] not in self.SEC:
|
|
# no security context established, cannot run an smc
|
|
self._log('WNG', 'require_smc: no CKSN set, unable to run an SMC')
|
|
return False
|
|
#
|
|
else:
|
|
return True
|
|
|
|
def get_smc_ies(self, cksn=None, newkey=False):
|
|
# if CKSN is None, take the 1st available
|
|
if cksn is None:
|
|
cksn = self._get_any_cksn()
|
|
try:
|
|
secctx = self.SEC[cksn]
|
|
except KeyError:
|
|
# no security ctxt available at all
|
|
self._log('WNG', 'no security context available, using SMC_DUMMY')
|
|
secctx = self.SMC_DUMMY
|
|
# prepare the IEs for encoding the SMC
|
|
IEs = {}
|
|
if self.SMC_UIA is not None:
|
|
IEs['IntegrityProtectionInformation'] = \
|
|
{'permittedAlgorithms': self.SMC_UIA,
|
|
'key': (bytes_to_uint(secctx['IK'], 128), 128)}
|
|
if self.SMC_UEA is not None:
|
|
IEs['EncryptionInformation'] = \
|
|
{'permittedAlgorithms': self.SMC_UEA,
|
|
'key': (bytes_to_uint(secctx['CK'], 128), 128)}
|
|
if newkey:
|
|
# taking a new context into use, i.e. just after an auth
|
|
IEs['KeyStatus'] = 'new'
|
|
else:
|
|
IEs['KeyStatus'] = 'old'
|
|
#
|
|
return IEs
|
|
|
|
def _get_any_cksn(self):
|
|
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
|
|
#
|
|
for i in range(0, 7):
|
|
if i in self.SEC:
|
|
self.SEC['CKSN'] = i
|
|
return i
|
|
return None
|
|
|
|
def get_new_cksn(self):
|
|
for i in range(0, 7):
|
|
if i not in self.SEC:
|
|
return i
|
|
# all CKSN have been used, clear all of them except the current one
|
|
cur = self.SEC['CKSN']
|
|
for i in range(0, 7):
|
|
if i != cur:
|
|
del self.SEC[i]
|
|
if cur == 0:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
def set_sec_ctx(self, cksn, ctx, vect):
|
|
if ctx == 3:
|
|
# 3G sec ctx
|
|
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_102_C4(vect[2]), conv_102_C5(vect[2])
|
|
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
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# network-initiated method (fg task, to be used from the interpreter)
|
|
#--------------------------------------------------------------------------#
|
|
|
|
def _send_to_rnc(self, buf):
|
|
if not self.RNC:
|
|
self._log('WNG', 'no RNC set, unable to send data')
|
|
return False
|
|
elif self.CtxId < 0:
|
|
self._log('WNG', 'no Iu context-id set, unable to send data in connected mode')
|
|
return False
|
|
else:
|
|
# start a RUADirectTransfer
|
|
ret = self.RNC.start_rua_proc(RUADirectTransfer, Context_ID=(self.CtxId, 24),
|
|
RANAP_Message=buf,
|
|
CN_DomainIndicator=self._cndomind)
|
|
return True if ret else False
|
|
|
|
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
|
|
"""
|
|
if not self.connected.is_set():
|
|
# nothing to release
|
|
self._log('DBG', 'release: UE not connected')
|
|
return True
|
|
# prepare the RANAPRelease procedure
|
|
RanapProc = self.init_ranap_proc(RANAPIuRelease, Cause=cause)
|
|
if not RanapProc:
|
|
return False
|
|
# encode the RANAP PDU and send it over RUA
|
|
return self._send_to_rnc_ranap([RanapProc])
|
|
|
|
def send_error_ind(self, cause, **IEs):
|
|
"""start a RANAPErrorIndCN with the given RANAP cause
|
|
|
|
IEs can contain any of the optional or extended IEs
|
|
"""
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# prepare the RANAP procedure
|
|
IEs['Cause'] = cause
|
|
RanapProc = self.init_ranap_proc(RANAPErrorIndCN, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# encode the RANAP PDU and send it over RUA
|
|
return self._send_to_rnc_ranap([RanapProc])
|
|
|
|
def send_common_id(self, **IEs):
|
|
"""start a RANAPCommonID with the UE's IMSI
|
|
|
|
IEs can contain any of the extended IEs
|
|
"""
|
|
if self.UE.IMSI is None:
|
|
return False
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# prepare the RANAP procedure
|
|
IEs['PermanentNAS_UE_ID'] = ('iMSI', NAS.encode_bcd(self.UE.IMSI))
|
|
RanapProc = self.init_ranap_proc(RANAPCommonID, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# encode the RANAP PDU and send it over RUA
|
|
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)
|
|
|
|
IEs can contain any of the optional or extended IEs
|
|
"""
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# prepare the RANAP procedure
|
|
IEs['TraceReference'] = traceref
|
|
RanapProc = self.init_ranap_proc(RANAPCNInvokeTrace, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# required for the logging within the procedure
|
|
RanapProc.TraceReference = traceref
|
|
# encode the RANAP PDU and send it over RUA
|
|
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)
|
|
and optional trigger id (2 or 3 bytes)
|
|
"""
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# prepare the RANAP procedure
|
|
IEs = {'TraceReference': traceref}
|
|
if isinstance(triggerid, bytes_types):
|
|
IEs['TriggerID'] = triggerid
|
|
RanapProc = self.init_ranap_proc(RANAPCNDeactivateTrace, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# required for the logging within the procedure
|
|
RanapProc.TraceReference = traceref
|
|
# encode the RANAP PDU and send it over RUA
|
|
return self._send_to_rnc_ranap([RanapProc])
|
|
|
|
def report_loc_ctrl(self, reqtype={'event':'direct', 'reportArea':'service-area', 'accuracyCode':0},
|
|
**IEs):
|
|
"""start a RANAPLocationReportingControl with a given request type
|
|
RequestType is a sequence of {event (enum), reportArea (enum), accuracyCode (int)}
|
|
|
|
IEs can contain any of the extended IEs
|
|
"""
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# prepare the RANAP procedure
|
|
IEs['RequestType'] = reqtype
|
|
RanapProc = self.init_ranap_proc(RANAPLocationReportingControl, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# required for the logging within the procedure
|
|
RanapProc.RequestType = reqtype
|
|
# encode the RANAP PDU and send it over RUA
|
|
return self._send_to_rnc_ranap([RanapProc])
|
|
|
|
def request_loc_data(self, reqtype={'requestedLocationRelatedDataType': 'decipheringKeysUEBasedOTDOA'},
|
|
**IEs):
|
|
"""start a RANAPLocationRelatedData with a given request type
|
|
RequestType is a sequence of {requestedLocationRelatedDataType (enum),
|
|
requestedGPSAssistanceData (octets, optional)}
|
|
|
|
IEs can contain any of the optional or extended IEs
|
|
"""
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# prepare the RANAP procedure
|
|
IEs['LocationRelatedDataRequestType'] = reqtype
|
|
RanapProc = self.init_ranap_proc(RANAPLocationRelatedData, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# encode the RANAP PDU and send it over RUA
|
|
return self._send_to_rnc_ranap([RanapProc])
|
|
|
|
def report_data_vol(self, rabidlist):
|
|
"""start a RANAPDataVolumeReport for the given list of RAB IDs (uint8)
|
|
"""
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# IE RAB-DataVolumeReportRequestList is a sequence of ProtocolIE-Container
|
|
# which is a sequence of ProtocolIE-Field
|
|
# with {id: 32, crit: reject, val: RAB-DataVolumeReportRequestItem}
|
|
# IE RAB-DataVolumeReportRequestItem is a sequence {RAB-ID (BIT STRING of size 8), iE-Extensions}
|
|
RIDList = []
|
|
for rabid in rabidlist:
|
|
RIDList.append({'id': 32, 'criticality': 'reject',
|
|
'value': ('RAB-DataVolumeReportRequestItem', {'rAB-ID': (rabid, 8)})})
|
|
# prepare the RANAP procedure
|
|
IEs = {'RAB_DataVolumeReportRequestList': [RIDList]}
|
|
RanapProc = self.init_ranap_proc(RANAPDataVolumeReport, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# encode the RANAP PDU and send it over RUA
|
|
return self._send_to_rnc_ranap([RanapProc])
|
|
|
|
def request_srns_ctxt(self, rabidlist):
|
|
"""start a RANAPSRNSContextTransfer for the given list of RAB IDs (uint8)
|
|
"""
|
|
if not self.connected.is_set():
|
|
# RANAP link disconnected
|
|
if self.RANAP_FORCE_PAGE:
|
|
# force to connect
|
|
if not self._net_init_con():
|
|
# unable to connect with the UE
|
|
return False
|
|
else:
|
|
return False
|
|
# IE RAB-DataForwardingList-SRNS-CtxReq is a sequence of ProtocolIE-Container
|
|
# which is a sequence of ProtocolIE-Field
|
|
# with {id: 27, crit: reject, val: RAB-DataForwardingItem-SRNS-CtxReq}
|
|
# IE RAB-DataForwardingItem-SRNS-CtxReq is a sequence {RAB-ID (BIT STRING of size 8), iE-Extensions}
|
|
RIDList = []
|
|
for rabid in rabidlist:
|
|
RIDList.append({'id': 27, 'criticality': 'reject',
|
|
'value': ('RAB-DataForwardingItem-SRNS-CtxReq', {'rAB-ID': (rabid, 8)})})
|
|
# prepare the RANAP procedure
|
|
IEs = {'RAB_DataForwardingList_SRNS_CtxReq': [RIDList]}
|
|
RanapProc = self.init_ranap_proc(RANAPSRNSContextTransfer, **IEs)
|
|
if not RanapProc:
|
|
return False
|
|
# encode the RANAP PDU and send it over RUA
|
|
return self._send_to_rnc_ranap([RanapProc])
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# NAS-related methods
|
|
#--------------------------------------------------------------------------#
|
|
|
|
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 = NasTx.to_bytes()
|
|
except Exception as 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)
|
|
if RanapProc:
|
|
return [RanapProc]
|
|
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
|
|
return NasProc.postprocess(RanapProc)
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# to send arbitrary NAS buffers to the UE
|
|
#--------------------------------------------------------------------------#
|
|
|
|
def send_nas_raw(self, naspdu, sapi=0, rx_hook=lambda x:[], wait_t=1):
|
|
"""Sends whatever bytes, or list of bytes, to the UE as NAS PDU(s)
|
|
"""
|
|
if not self._net_init_con():
|
|
return False
|
|
#
|
|
self.RX_HOOK = rx_hook
|
|
if sapi == 3:
|
|
sapi = 'sapi-3'
|
|
else:
|
|
sapi = 'sapi-0'
|
|
#
|
|
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:
|
|
return False
|
|
#
|
|
del self.RX_HOOK
|
|
return True
|
|
|