corenet: continue support for gNB

This commit is contained in:
mich 2020-04-29 11:58:06 +02:00
parent 55628841d0
commit f1f73b7a8d
2 changed files with 298 additions and 64 deletions

View File

@ -4,6 +4,7 @@
# * Version : 0.4
# *
# * Copyright 2017. Benoit Michau. ANSSI.
# * Copyright 2020. Benoit Michau. P1Sec.
# *
# * This library is free software; you can redistribute it and/or
# * modify it under the terms of the GNU Lesser General Public
@ -31,8 +32,9 @@
# This is the main corenet server
#
# It serves connection to:
# - Home-NodeB over HNBAP and RUA / RANAP
# - eNodeB and Home-eNodeB over S1AP
# - Home-NodeB over HNBAP and RUA
# - gNodeB over NGAP
#
# It handles signalling trafic for UE
# and connects them to specific service handler (SMS, GTPU, ...)
@ -41,6 +43,7 @@
from .utils import *
from .HdlrHNB import HNBd
from .HdlrENB import ENBd
from .HdlrGNB import GNBd
from .HdlrUE import UEd
from .ServerAuC import AuC
from .ServerGTPU import ARPd, GTPUd, BLACKHOLE_LAN, BLACKHOLE_WAN
@ -124,12 +127,32 @@ class CorenetServer(object):
#
# main PLMN served
PLMN = '00101'
# equivalent PLMNs served, used for Iu and S1 interface
# None or list of PLMNs ['30124', '763326', ...]
EQUIV_PLMN = None
#
# AMF RegionID, SetID and Pointer
AMF_RID = 1
AMF_SID = 1
AMF_PTR = 0
# list of PLMN and slices supported
# a sliceSupportList is basically a Sequence of S-NSSAI
AMF_SNSSAI = {
1 : {'sST': b'\x01'},
2 : {'sST': b'\x02'},
21 : {'sST': b'\x02', 'sD': b'\0\0\x01'},
}
AMF_PLMNSupp = [
#{'pLMNIdentity': $plmn1,
# 'sliceSupportList': [{'s-NSSAI': AMF_SNSSAI[1]}]},
#{'pLMNIdentity': $plmn2,
# 'sliceSupportList': [{'s-NSSAI': AMF_SNSSAI[2]}, {'s-NSSAI': AMF_SNSSAI[21]}]},
]
#
# MME GroupID and Code
MME_GID = 1
MME_CODE = 1
# equivalent PLMNs served
# None or list of PLMNs ['30124', '763326', ...]
EQUIV_PLMN = None
#
# emergency number lists
# None or list of 2-tuple [(number_category, number), ...]
# number_category is a set of strings: 'Police', 'Ambulance', 'Fire', 'Marine', 'Mountain'
@ -140,6 +163,21 @@ class CorenetServer(object):
# ({'Marine', 'Mountain'}, '112113')]
EMERG_NUMS = None
#
# N2 connection AMF parameters
ConfigN2 = {
'AMFName' : 'CorenetAMF',
'PLMNSupportList' : AMF_PLMNSupp,
'RelativeAMFCapacity': 10,
'ServedGUAMIList' : [
{'gUAMI': {
'pLMNIdentity': plmn_str_to_buf(PLMN),
'aMFRegionID' : (AMF_RID, 8),
'aMFSetID' : (AMF_SID, 10),
'aMFPointer' : (AMF_PTR, 6)}},
],
#'UERetentionInformation': 'ues-retained',
}
#
# S1 connection MME parameters
ConfigS1 = {
'MMEname': 'CorenetMME',
@ -152,6 +190,7 @@ class CorenetServer(object):
'EquivPLMNList' : EQUIV_PLMN,
'EmergNumList' : EMERG_NUMS,
}
#
# HNBAP connection GW parameters (keep it empty)
ConfigHNBAP = {}
# RUA connection GW parameters (keep it empty)
@ -313,25 +352,14 @@ class CorenetServer(object):
#--------------------------------------------------------------------------#
def start(self, serving=True):
# start SCTP servers, bind() and listen()
self.SCTPServ = [] # will be casted to tuple
#
if DEBUG_SK:
self._skc = []
# LUT for connected SCTP client and ENBId / HNBId
self.SCTPCli = {}
#
if self.SERVER_HNB:
self._start_hnb_server()
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 )
else:
self._sk_enb = None
self.SCTPServ = tuple(self.SCTPServ)
# start SCTP servers, bind() and listen()
self._start_server()
#
# init the dict for storing UE with unknown IMSI at attachment
self._UEpre = {}
@ -369,45 +397,30 @@ class CorenetServer(object):
def is_running(self):
return self._running
def _start_hnb_server(self):
# start SCTP server for Home-NodeBs
server_addr = (self.SERVER_HNB['IP'], self.SERVER_HNB['port'])
try:
self._sk_hnb = sctp.sctpsocket_tcp(self.SERVER_HNB['INET'])
self.sctp_set_events(self._sk_hnb)
except Exception as err:
raise(CorenetErr('cannot create SCTP socket: {0}'.format(err)))
try:
self._sk_hnb.bind(server_addr)
except Exception as err:
raise(CorenetErr('cannot bind SCTP socket on address {0!r}: {1}'\
.format(server_addr, err)))
try:
self._sk_hnb.listen(self.SERVER_HNB['MAXCLI'])
except Exception as err:
raise(CorenetErr('cannot listen to SCTP connection: {1}'.format(err)))
#
self._log('INF', 'SCTP HNB server started on address %r' % (server_addr, ))
def _start_enb_server(self):
# start SCTP server for eNodeBs
server_addr = (self.SERVER_ENB['IP'], self.SERVER_ENB['port'])
try:
self._sk_enb = sctp.sctpsocket_tcp(self.SERVER_ENB['INET'])
self.sctp_set_events(self._sk_enb)
except Exception as err:
raise(CorenetErr('cannot create SCTP socket: {0}'.format(err)))
try:
self._sk_enb.bind(server_addr)
except Exception as err:
raise(CorenetErr('cannot bind SCTP socket on address {0!r}: {1}'\
.format(server_addr, err)))
try:
self._sk_enb.listen(self.SERVER_ENB['MAXCLI'])
except Exception as err:
raise(CorenetErr('cannot listen to SCTP connection: {1}'.format(err)))
#
self._log('INF', 'SCTP ENB server started on address %r' % (server_addr, ))
def _start_server(self):
self.SCTPServ = []
for (cfg, attr) in ((self.SERVER_HNB, '_sk_hnb',
(self.SERVER_ENB, '_sk_enb',
(self.SERVER_GNB, '_sk_gnb')):
if 'INET' not in cfg or 'IP' not in cfg \
or 'port' not in cfg or 'MAXCLI' not in cfg:
setattr(self, attr, None)
continue
try:
sk = sctp.sctpsocket_tcp(cfg['INET'])
addr = (cfg['IP'], cfg['port'])
srv = attr[-3:].upper()
self.sctp_set_events(sk)
except Exception as err:
raise(CorenetErr('cannot create SCTP socket: %s' % err))
try:
sk.bind(addr)
except Exception as err:
raise(CorenetErr('cannot bind SCTP socket on addr %r: %s' % (addr, err)))
self._log('INF', 'SCTP %s server started on address %r' % (srv, addr))
setattr(self, attr, sk)
self.SCTPServ.append(sk)
self.SCTPServ = tuple(self.SCTPServ)
def _serve(self):
# Main server loop, using select() to read sockets, the loop:
@ -424,7 +437,10 @@ class CorenetServer(object):
self._running = False
#
for sk in skr:
if sk == self._sk_enb:
if sk == self._sk_gnb:
# new gNodeB SCTP client (NGSetupRequest)
self.handle_new_gnb()
elif sk == self._sk_enb:
# new eNodeB STCP client (S1SetupRequest)
self.handle_new_enb()
elif sk == self._sk_hnb:
@ -445,15 +461,18 @@ class CorenetServer(object):
def stop(self):
self._running = False
asn_ngap_release()
asn_s1ap_release()
asn_hnbap_release()
asn_rua_release()
asn_ranap_release()
sleep(self.SCHED_RES + 0.01)
if hasattr(self, '_sk_hnb') and self._sk_hnb:
if self._sk_hnb is not None:
self._sk_hnb.close()
if hasattr(self, '_sk_enb') and self._sk_enb:
if self._sk_enb is not None:
self._sk_enb.close()
if self._sk_gnb is not None:
self._sk_gnb.close()
self._clean_ue_proc.join()
#
# disconnect all RAN clients
@ -534,6 +553,11 @@ class CorenetServer(object):
# remove from the Server location tables
if cli.Config:
self._unset_enb_loc(cli)
elif isinstance(cli, GNBd):
self._log('DBG', 'gNB %s closed connection' % (cli.ID,))
# remove from the Server location tables
if cli.Config:
self._unset_gnb_loc(cli)
else:
assert()
# update HNB / ENB state
@ -631,8 +655,8 @@ class CorenetServer(object):
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'))
Err = enb.init_s1ap_proc(S1APErrorIndNonUECN,
Cause=('protocol', 'transfer-syntax-error'))
pdu_tx = Err.send()
else:
pdu_rx = PDU_S1AP()
@ -648,6 +672,35 @@ class CorenetServer(object):
for pdu in pdu_tx:
self.send_s1ap_pdu(enb, pdu, sid)
#
elif ppid == SCTP_PPID_NGAP:
assert( isinstance(ran, GNBd) )
gnb = ran
if not asn_ngap_acquire():
gnb._log('ERR', 'unable to acquire the NGAP module')
return
try:
PDU_NGAP.from_aper(buf)
except:
asn_ngap_release()
gnb._log('WNG', 'invalid NGAP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
Err = gnb.init_ngap_proc(NGAPErrorIndNonUECN,
Cause=('protocol', 'transfer-syntax-error'))
pdu_tx = Err.send()
else:
pdu_rx = PDU_S1AP()
if enb.TRACE_ASN_NGAP:
enb._log('TRACE_ASN_NGAP_UL', PDU_NGAP.to_asn1())
asn_ngap_release()
if sid == gnb.SKSid:
# non-UE-associated signalling
pdu_tx = gnb.process_ngap_pdu(pdu_rx)
else:
# UE-associated signalling
pdu_tx = enb.process_ngap_ue_pdu(pdu_rx, sid)
for pdu in pdu_tx:
self.send_ngap_pdu(gnb, pdu, sid)
#
else:
self._log('ERR', 'invalid SCTP PPID, %i' % ppid)
if self.SERVER_HNB['errclo']:
@ -687,6 +740,161 @@ class CorenetServer(object):
asn_s1ap_release()
return self._write_sk(enb.SK, buf, ppid=SCTP_PPID_S1AP, stream=sid)
def send_ngap_pdu(self, gnb, pdu, sid):
if not asn_ngap_acquire():
gnb._log('ERR', 'unable to acquire the NGAP module')
return
PDU_NGAP.set_val(pdu)
if gnb.TRACE_ASN_NGAP:
gnb._log('TRACE_ASN_NGAP_DL', PDU_NGAP.to_asn1())
buf = PDU_NGAP.to_aper()
asn_ngap_release()
return self._write_sk(gnb.SK, buf, ppid=SCTP_PPID_NGAP, stream=sid)
#--------------------------------------------------------------------------#
# gNodeB connection
#--------------------------------------------------------------------------#
def _parse_ngsetup(self, pdu):
if pdu[0] != 'initiatingMessage' or pdu[1]['procedureCode'] != 21:
# not initiating / NGSetup
self._log('WNG', 'invalid NGAP PDU for setting up the gNB NGAP link')
return
#
pIEs, plmn, ranid = 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'] == 27:
# GlobalRANNodeID:
# PLMN,
# ID type (gNB-ID, macroNgENB-ID, shortMacroNgENB-ID, longMacroNgENB-ID or n3IWF-ID),
# ID bit-string value
ranid = ngranid_to_hum(ie['value'][1])
plmn = ranid[0]
ranid = ranid[1:]
break
if plmn is None or ranid is None:
self._log('WNG', 'invalid NGAP PDU for setting up the gNB NGAP link: '\
'missing PLMN and RAN-ID')
return
# decode PLMN and CellID
return plmn, ranid
def _send_ngsetuprej(self, sk, cause):
IEs = [{'criticality': 'ignore',
'id': 15, # id-Cause
'value': (('NGAP-IEs', 'Cause'), cause)}]
pdu = ('unsuccessfulOutcome',
{'criticality': 'ignore',
'procedureCode': 21,
'value': (('NGAP-PDU-Contents', 'NGSetupFailure'),
{'protocolIEs' : IEs})})
if not asn_ngap_acquire():
self._log('ERR', 'unable to acquire the NGAP module')
else:
PDU_NGAP.set_val(pdu)
if GNBd.TRACE_ASN_NGAP:
self._log('TRACE_ASN_NGAP_DL', PDU_NGAP.to_asn1())
self._write_sk(sk, PDU_NGAP.to_aper(), ppid=SCTP_PPID_NGAP, stream=0)
asn_ngap_release()
if self.SERVER_GNB['errclo']:
sk.close()
def handle_new_gnb(self):
sk, addr = self._sk_gnb.accept()
self._log('DBG', 'New gNB client from address %r' % (addr, ))
#
buf, notif = self._read_sk(sk)
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_NGAP:
self._log('ERR', 'invalid NGAP PPID, %i' % ppid)
if self.SERVER_GNB['errclo']:
sk.close()
return
#
if not asn_ngap_acquire():
self._log('ERR', 'unable to acquire the NGAP module')
return
try:
PDU_NGAP.from_aper(buf)
except:
self._log('WNG', 'invalid NGAP PDU transfer-syntax: %s'\
% hexlify(buf).decode('ascii'))
# return nothing, no need to bother
return
if GNBd.TRACE_ASN_NGAP:
self._log('TRACE_ASN_NGAP_UL', PDU_NGAP.to_asn1())
pdu_rx = PDU_NGAP()
asn_ngap_release()
#
GNBId = self._parse_ngsetup(pdu_rx)
if GNBId is None:
# send NGSetupReject
self._send_ngsetuprej(sk, cause=('protocol', 'abstract-syntax-error-reject'))
return
elif GNBId not in self.RAN:
if not self.RAN_CONNECT_ANY:
self._log('ERR', 'gNB %r not allowed to connect' % (GNBId,))
# send NGSetupReject
self._send_ngsetuprej(sk, cause=('radioNetwork', 'unspecified'))
return
elif GNBId[0] not in self.RAN_ALLOWED_PLMN:
self._log('ERR', 'gNB %r not allowed to connect, bad PLMN' % (GNBId,))
self._send_ngsetuprej(sk, cause=('radioNetwork', 'unspecified'))
return
else:
# creating an entry for this gNB
gnb = GNBd(self, sk, sid)
self.RAN[GNBId] = gnb
else:
if self.RAN[GNBId] is None:
# gNB allowed, but not yet connected
gnb = GNBd(self, sk, sid)
self.RAN[GNBId] = gnb
elif not self.RAN[GNBId].is_connected():
# gNB already connected and disconnected in the past
gnb = self.RAN[GNBId]
gnb.__init__(self, sk, sid)
else:
# gNB already connected
self._log('ERR', 'gNB %r already connected from address %r'\
% (GNBId, self.RAN[GNBId].SK.getpeername()))
if self.SERVER_GNB['errclo']:
sk.close()
return
#
# process the initial PDU
pdu_tx = gnb.process_ngap_pdu(pdu_rx)
# keep track of the client
self.SCTPCli[sk] = GNBId
# add the gnb TAI to the Server location tables
if gnb.Config:
self._set_gnb_loc(gnb)
#
# send available PDU(s) back
if not asn_ngap_acquire():
gnb._log('ERR', 'unable to acquire the NGAP module')
return
for pdu in pdu_tx:
PDU_NGAP.set_val(pdu)
if GNBd.TRACE_ASN_S1AP:
gnb._log('TRACE_ASN_NGAP_DL', PDU_NGAP.to_asn1())
self._write_sk(sk, PDU_NGAP.to_aper(), ppid=SCTP_PPID_NGAP, stream=sid)
asn_ngap_release()
# in 5G, gNB are dealing with TA more or less in the same way as in 4G
_set_gnb_loc = _set_enb_loc
_unset_gnb_loc = _unset_enb_loc
#--------------------------------------------------------------------------#
# eNodeB connection
#--------------------------------------------------------------------------#
@ -696,7 +904,7 @@ class CorenetServer(object):
# 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:
@ -842,7 +1050,7 @@ class CorenetServer(object):
try:
self.TAI[tai].remove(enb.ID)
except:
self._log('ERR', 'ENB not referenced into the TAI table')
self._log('ERR', 'RAN node %r not referenced into the TAI table' % (enb.ID,))
#--------------------------------------------------------------------------#
# Home-NodeB connection

View File

@ -4,6 +4,7 @@
# * Version : 0.4
# *
# * Copyright 2017. Benoit Michau. ANSSI.
# * Copyright 2020. Benoit Michau. P1Sec.
# *
# * This library is free software; you can redistribute it and/or
# * modify it under the terms of the GNU Lesser General Public
@ -577,6 +578,31 @@ def inet_ntoa_cn(pdntype, buf, dom='EPS'):
else:
return (pdntype, buf)
def ngranid_to_hum(cho):
if cho[0] == 'globalGNB-ID':
# std gNB-ID
return (
plmn_buf_to_str(cho[1]['pLMNIdentity']),
cho[1]['gNB-ID'][0],
cho[1]['gNB-ID'][1]
)
elif cho[0] == 'globalNgENB-ID':
# std ng-eNB-ID
return (
plmn_buf_to_str(cho[1]['pLMNIdentity']),
cho[1]['ngENB-ID'][0],
cho[1]['ngENB-ID'][1]
)
elif cho[0] == 'globalN3IWF-ID':
return (
plmn_buf_to_str(cho[1]['pLMNIdentity']),
cho[1]['n3IWF-ID'][0],
cho[1]['n3IWF-ID'][1]
)
return None
#------------------------------------------------------------------------------#
# ASN.1 object handling facilities
#------------------------------------------------------------------------------#