452 lines
14 KiB
Python
452 lines
14 KiB
Python
# -*- coding: UTF-8 -*-
|
|
#/**
|
|
# * Software Name : pycrate
|
|
# * Version : 0.4
|
|
# *
|
|
# * Copyright 2020. 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/HdlrUENG.py
|
|
# * Created : 2020-04-29
|
|
# * Authors : Benoit Michau
|
|
# *--------------------------------------------------------
|
|
#*/
|
|
|
|
from .utils import *
|
|
from .ProcCNNgap import *
|
|
# load all required 5G NAS protocol handlers and SMS handler
|
|
#from .ProcCNFGMM import *
|
|
#from .HdlrUESMS import *
|
|
|
|
|
|
class UEFGMMd(SigStack):
|
|
"""UE 5GMM handler within a UENGd instance
|
|
responsible for 5G Mobility Management signalling procedures
|
|
"""
|
|
|
|
TRACK_PROC = True
|
|
|
|
# reference to the UEd
|
|
UE = None
|
|
# reference to the UENGd
|
|
NG = None
|
|
|
|
# state: INACTIVE (cannot be paged) <-> ACTIVE <-> IDLE
|
|
state = 'INACTIVE'
|
|
|
|
# to bypass the process() server loop with a custom NAS PDU handler
|
|
RX_HOOK = None
|
|
|
|
# additional time for letting background task happen in priority
|
|
_WAIT_ADD = 0.005
|
|
|
|
# list of 5GMM message types that do not require NAS security to be
|
|
# activated to be processed
|
|
SEC_NOTNEED = {}
|
|
# to disable completely the check for secured NAS message
|
|
SEC_DISABLED = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _log(self, logtype, msg):
|
|
self.NG._log(logtype, '[5GMM] %s' % msg)
|
|
|
|
def __init__(self, ued, uengd):
|
|
self.UE = ued
|
|
self.set_ng(uengd)
|
|
#
|
|
# ready event, used by foreground tasks (network / interpreter initiated)
|
|
self.ready = Event()
|
|
self.ready.set()
|
|
# stack of ongoing 5GMM procedures (i.e. common procedures can be run
|
|
# within specific procedure)
|
|
self.Proc = []
|
|
# list of tracked procedures (requires TRACK_PROC = True)
|
|
self._proc = []
|
|
|
|
def set_ng(self, uengd):
|
|
self.NG = uengd
|
|
|
|
def process(self, NasRx):
|
|
"""process a NAS 5GMM message (NasRx) sent by the UE,
|
|
and return a list (possibly empty) of NGAP procedure(s) to be sent back
|
|
to the gNB
|
|
|
|
NasRx has 2 additional attributes (_sec [bool], _ulcnt [uint])
|
|
"""
|
|
pass
|
|
|
|
def init_proc(self, ProcClass, encod=None, fgmm_preempt=False, sec=True):
|
|
"""initialize a CN-initiated 5GMM procedure of class `ProcClass' and
|
|
given encoder(s), and return the procedure
|
|
"""
|
|
pass
|
|
|
|
def clear(self):
|
|
"""abort all running procedures
|
|
"""
|
|
for Proc in self.Proc[::-1]:
|
|
Proc.abort()
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# SMC and security-related methods
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# network-initiated method (fg task, to be used from the interpreter)
|
|
#--------------------------------------------------------------------------#
|
|
|
|
def _net_init_con(self):
|
|
if not self.NG.page_block():
|
|
return False
|
|
# need to wait for potential 5GMM serving / common procedures to happen and end
|
|
sleep(self._WAIT_ADD)
|
|
if not self.ready.wait(10):
|
|
# something is blocking in the serving / common procedures
|
|
return False
|
|
elif not self.NG.connected.is_set():
|
|
# something went wrong during the serving / common procedures
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def run_proc(self, ProcClass, sec=True, **IEs):
|
|
"""run a network-initiated procedure ProcClass in the context of the 5GMM stack,
|
|
after setting the given IEs in the NAS message to be sent to the UE
|
|
|
|
returns a 2-tuple (success, proc)
|
|
success is a bool
|
|
proc is the instance of ProcClass or None
|
|
"""
|
|
if ProcClass.Init is None:
|
|
self._log('ERR', 'invalid network-initiated procedure %s' % ProcClass.Name)
|
|
return False, None
|
|
if not self._net_init_con():
|
|
return False, None
|
|
#
|
|
Proc = self.init_proc(ProcClass, encod={ProcClass.Init: IEs}, fgmm_preempt=True, sec=sec)
|
|
try:
|
|
NgapTxProc = Proc.output()
|
|
except Exception:
|
|
self._log('ERR', 'invalid IEs for network-initiated procedure %s' % Proc.Name)
|
|
Proc.abort()
|
|
return False, Proc
|
|
if not self.NG.transmit_ngap_proc(NgapTxProc):
|
|
return False, Proc
|
|
#
|
|
# check if a response is expected
|
|
if not hasattr(Proc, 'TimerValue'):
|
|
return True, Proc
|
|
elif not self.ready.wait(Proc.TimerValue + self._WAIT_ADD):
|
|
# procedure is stuck, will be aborted in the server loop
|
|
# WNG: this means the routine for cleaning NAS procedures in timeout
|
|
# should be enabled in CorenetServer
|
|
return False, Proc
|
|
#
|
|
# check if a response was received
|
|
if hasattr(Proc, 'UEInfo'):
|
|
return True, Proc
|
|
else:
|
|
return False, Proc
|
|
|
|
|
|
|
|
|
|
class UEFGSMd(SigStack):
|
|
"""UE 5GSM handler within a UENGd instance
|
|
responsible for 5G Session Management signalling procedures
|
|
"""
|
|
|
|
TRACK_PROC = True
|
|
|
|
# reference to the UEd
|
|
UE = None
|
|
# reference to the UENGd
|
|
NG = None
|
|
|
|
# to bypass the process() server loop with a custom NAS PDU handler
|
|
RX_HOOK = None
|
|
|
|
# list of ESM message types that do not require NAS security to be
|
|
# activated to be processed
|
|
SEC_NOTNEED = {
|
|
}
|
|
# to disable completely the check for secured NAS message
|
|
SEC_DISABLED = False
|
|
|
|
|
|
|
|
|
|
def _log(self, logtype, msg):
|
|
self.S1._log(logtype, '[5GSM] %s' % msg)
|
|
|
|
def __init__(self, ued, uengd):
|
|
self.UE = ued
|
|
self.set_ng(uengd)
|
|
#
|
|
# dict of ongoing 5GSM procedures, indexed by 5GS bearer ID
|
|
self.Proc = {i: [] for i in range(16)}
|
|
# dict of configured PDN, indexed by 5GS bearer ID
|
|
self.PDN = {}
|
|
# dict of ongoing 5GSM transactions IEs
|
|
self.Trans = {}
|
|
# list of tracked procedures (requires TRACK_PROC = True)
|
|
self._proc = []
|
|
|
|
def set_ng(self, uengd):
|
|
self.NG = uengd
|
|
|
|
def process_buf(self, buf, sec, EMMProc=None):
|
|
"""process a NAS 5GSM message buffer (buf) sent by the UE,
|
|
if the decoding is correct, return the result of process()
|
|
"""
|
|
return []
|
|
|
|
def process(self, NasRx, FMMProc=None):
|
|
"""process a NAS 5GSM message (NasRx) sent by the UE,
|
|
and return a list (possibly empty) of NGAP procedure(s) to be sent back
|
|
to the gNB
|
|
|
|
NasRx has 2 additional attributes (_sec [bool], _ulcnt [uint])
|
|
|
|
FGMMProc [FMMSigProc or None], indicates if the NAS FGSM message is handled in
|
|
the context of an FGMM procedure
|
|
"""
|
|
return []
|
|
|
|
|
|
def init_proc(self, ProcClass, **kw):
|
|
"""initialize a CN-initiated 5GSM procedure of class `ProcClass' and
|
|
given encoder(s), and return the procedure
|
|
"""
|
|
pass
|
|
|
|
def clear(self, ebi=None):
|
|
"""abort all running procedures, eventually for a single 5GS Bearer ID
|
|
"""
|
|
pass
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# transaction processing
|
|
#--------------------------------------------------------------------------#
|
|
|
|
def process_trans(self, trans_id):
|
|
"""process a 5GSM transaction initiated by the UE, and return a network-initiated
|
|
procedure with IEs configured and None, or None and the 5GSM error code
|
|
"""
|
|
pass
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# protocol configuration processing
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
|
|
|
|
|
class UENGd(SigStack):
|
|
"""UE NG handler within a CorenetServer instance
|
|
responsible for UE-associated NGAP signalling
|
|
"""
|
|
|
|
# to keep track of all NGAP procedures
|
|
TRACK_PROC = True
|
|
|
|
# domain
|
|
DOM = '5GS'
|
|
|
|
# reference to the UEd
|
|
UE = None
|
|
# reference to the GNBd, SCTP stream id
|
|
GNB = None
|
|
SID = None
|
|
|
|
# to bypass the process_nas() server loop with a custom NAS PDU handler
|
|
RX_HOOK = None
|
|
|
|
# for pure NGAP procedure (no NAS trafic, neither RAB-oriented stuff)
|
|
# should we page the UE to run the procedure successfully when UE is idle
|
|
NGAP_FORCE_PAGE = False
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# global security policy
|
|
#--------------------------------------------------------------------------#
|
|
# this will systematically bypass all auth and smc procedures,
|
|
# NAS MAC and UL count verification in the uplink
|
|
# and setting of the 5GMM security header (and encryption) in the downlink
|
|
SECNAS_DISABLED = False
|
|
#
|
|
# finer grained NAS security checks:
|
|
# True to drop NAS PDU when NAS MAC verification fails
|
|
SECNAS_UL_MAC = False
|
|
# True to drop NAS PDU when NAS UL count verification fails
|
|
SECNAS_UL_CNT = False
|
|
# WNG: 5GMM and 5GSM stacks have further control on accepting or not certain
|
|
# NAS message even if security control have failed
|
|
#
|
|
# this will disable the setting of the 5GMM security header (and encryption)
|
|
# in the downlink for given NAS message (by name)
|
|
SECNAS_PDU_NOSEC = set()
|
|
#
|
|
# format of the security context dict self.SEC:
|
|
# TODO
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# NGAPPaging policy
|
|
#--------------------------------------------------------------------------#
|
|
#
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# NGAPInitialContextSetup policy
|
|
#--------------------------------------------------------------------------#
|
|
#
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# NGAPTraceStart policy
|
|
#--------------------------------------------------------------------------#
|
|
#
|
|
|
|
|
|
def _log(self, logtype, msg):
|
|
self.UE._log(logtype, '[UENGd: %3i] %s' % (self.CtxId, msg))
|
|
|
|
def __init__(self, ued, gnbd=None, ctx_id=-1, sid=None):
|
|
self.UE = ued
|
|
self.Server = ued.Server
|
|
self.Config = self.Server.ConfigNG
|
|
#
|
|
# dict of ongoing NGAP procedures (indexed by their procedure code)
|
|
self.Proc = {}
|
|
# list of tracked procedures (requires TRACK_PROC = True)
|
|
self._proc = []
|
|
#
|
|
# dict of available 5G security contexts, indexed by KSI
|
|
# and current KSI in use
|
|
self.SEC = {}
|
|
self.reset_sec_ctx()
|
|
#
|
|
self.connected = Event()
|
|
if gnbd is not None:
|
|
self.set_ran(gnbd)
|
|
else:
|
|
self.CtxId = -1
|
|
#
|
|
# init 5GMM and 5GSM sig stacks
|
|
self.FGMM = UEFGMMd(ued, self)
|
|
self.FGSM = UEFGSMd(ued, self)
|
|
#self.SMS = UESMSd(ued, self)
|
|
|
|
def set_ran(self, gnbd):
|
|
self.SEC['KSI'] = None
|
|
self.GNB = gnbd
|
|
self.connected.set()
|
|
|
|
def unset_ran(self):
|
|
self.GNB.unset_ue_ng(self.CtxId)
|
|
del self.GNB
|
|
self.SEC['KSI'] = None
|
|
self.clear()
|
|
self.connected.clear()
|
|
|
|
def set_ran_unconnected(self, gnbd):
|
|
# required for paging
|
|
self.SEC['KSI'] = None
|
|
self.GNB = gnbd
|
|
|
|
def unset_ran_unconnected(self):
|
|
# required for paging
|
|
del self.GNB
|
|
self.SEC['KSI'] = None
|
|
|
|
def is_connected(self):
|
|
return self.connected.is_set()
|
|
|
|
def set_ctx(self, ctx_id, sid):
|
|
self.CtxId = ctx_id
|
|
self.SID = sid
|
|
|
|
def unset_ctx(self):
|
|
self.CtxId = -1
|
|
del self.SID
|
|
|
|
def reset_sec_ctx(self):
|
|
self.SEC.clear()
|
|
self.SEC['KSI'] = None
|
|
self.SEC['POL'] = {'TAU': 0, 'DET': 0, 'SER': 0}
|
|
if 'UESecCap' in self.UE.Cap:
|
|
del self.UE.Cap['UESecCap']
|
|
|
|
def get_sec_ctx(self):
|
|
return self.SEC.get(self.SEC['KSI'], None)
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# handling of NGAP procedures
|
|
#--------------------------------------------------------------------------#
|
|
|
|
def process_ngap_pdu(self, pdu_rx):
|
|
"""process a NGAP PDU sent by the gNB for UE-associated signalling
|
|
and return a list of NGAP PDU(s) to be sent back to it
|
|
"""
|
|
pass
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# handling of NAS messages dispatching
|
|
#--------------------------------------------------------------------------#
|
|
|
|
def process_nas(self, buf):
|
|
"""process a NAS message buffer for the 5GS domain sent by the mobile
|
|
and return a list (possibly empty) of NGAP procedure(s) to be sent back
|
|
to the gNB
|
|
"""
|
|
pass
|
|
|
|
|
|
def clear_nas_proc(self):
|
|
# clears all NAS EPS procedures
|
|
self.FGMM.clear()
|
|
self.FGSM.clear()
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# network-initiated method (fg task, to be used from the interpreter)
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# to send arbitrary NAS buffers to the UE
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
# 5G bearer activation
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|