Ejemplo n.º 1
0
 def handle_s1setup(self, sk, pdu):
     # get protocolIEs' values
     protIEs = pdu[1]['value'][1]['protocolIEs']
     # get the eNB Global ID
     plmn_id, enb_id = None, None
     for protIE in protIEs:
         if protIE['id'] == 59:
             enb_id_bitstr = protIE['value'][1]['eNB-ID'][1]
             plmn_id_octstr = protIE['value'][1]['pLMNidentity']
             # convert to readable values:
             # 1) enb id
             if enb_id_bitstr[1] == 20:
                 # macroENB-ID
                 enb_id = '%.5x' % enb_id_bitstr[0]
             elif enb_id_bitstr[1] == 28:
                 # homeENB-ID
                 enb_id = '%.7x' % enb_id_bitstr[0]
             # 2) plmn id
             plmn_id = PLMN()
             plmn_id.map(plmn_id_octstr)
             plmn_id = plmn_id.get_mccmnc()
     #
     if plmn_id and enb_id:
         enb_gid = (plmn_id, enb_id)
         # create / reset the eNB handler instance
         ret = self.create_enb_handler(enb_gid, sk)
         if not ret:
             # eNB not allowed
             self.send_enb_err(sk, cause=('misc', 'unknown-PLMN'))
             self._log(
                 'WNG',
                 '[eNB: {0}] eNB not allowed, closing S1AP stream'.format(
                     enb_gid))
             sk.close()
             return
         #
         self._log('INF',
                   '[eNB: {0}] S1AP stream established'.format(enb_gid))
         # process the PDU in the context of the eNB handler
         #'''
         try:
             ret_pdu = self.ENB[enb_gid].process_pdu(pdu)
         except Exception as err:
             self._exc = sys.exc_info()
             self._log(
                 'ERR',
                 'something went wrong in the ENBmgr code: {0}'.format(err))
             print('ERROR: something went wrong in the ENBmgr code:\n{0}'.
                   format(err))
             traceback.print_tb(self._exc[2])
         else:
             self.add_enb(enb_gid)
             for pdu in ret_pdu:
                 self.send_enb(sk, pdu)
Ejemplo n.º 2
0
Archivo: MME.py Proyecto: ewa/libmich
 def handle_s1setup(self, sk, pdu):
     # get protocolIEs' values
     protIEs = pdu[1]['value'][1]['protocolIEs']
     # get the eNB Global ID
     plmn_id, enb_id = None, None
     for protIE in protIEs:
         if protIE['id'] == 59:
             enb_id_bitstr = protIE['value'][1]['eNB-ID'][1]
             plmn_id_octstr = protIE['value'][1]['pLMNidentity']
             # convert to readable values:
             # 1) enb id
             if enb_id_bitstr[1] == 20:
                 # macroENB-ID
                 enb_id = '%.5x' % enb_id_bitstr[0]
             elif enb_id_bitstr[1] == 28:
                 # homeENB-ID
                 enb_id = '%.7x' % enb_id_bitstr[0]
             # 2) plmn id
             plmn_id = PLMN()
             plmn_id.map(plmn_id_octstr)
             plmn_id = plmn_id.get_mccmnc()
     #
     if plmn_id and enb_id:
         enb_gid = (plmn_id, enb_id)
         # create / reset the eNB handler instance
         ret = self.create_enb_handler(enb_gid, sk)
         if not ret:
             # eNB not allowed
             self.send_enb_err(sk, cause=('misc', 'unknown-PLMN'))
             self._log('WNG', '[eNB: {0}] eNB not allowed, closing S1AP stream'.format(enb_gid))
             sk.close()
             return
         #
         self._log('INF', '[eNB: {0}] S1AP stream established'.format(enb_gid))
         # process the PDU in the context of the eNB handler
         #'''
         try:
             ret_pdu = self.ENB[enb_gid].process_pdu(pdu)
         except Exception as err:
             self._exc = sys.exc_info()
             self._log('ERR', 'something went wrong in the ENBmgr code: {0}'.format(err))
             print('ERROR: something went wrong in the ENBmgr code:\n{0}'.format(err))
             traceback.print_tb(self._exc[2])
         else:
             self.add_enb(enb_gid)
             for pdu in ret_pdu:
                 self.send_enb(sk, pdu)
Ejemplo n.º 3
0
class SupportedTAs(Layer):
    _byte_aligned = False
    constructorList = [
        Bit('pad', BitLen=10, Repr='bin'),
        Bit('TAC', BitLen=16, Repr='hex'),
        Bit('F_PLMN', BitLen=6, Repr='bin'),
        PLMN(),
    ]
Ejemplo n.º 4
0
class GlobalENBID(Layer):
    _byte_aligned = False
    constructorList = [
        Int('F_PLMN', Type='uint8', Repr='hex'),
        PLMN(),
        Int('F_eNBID', Type='uint8', Repr='hex'),
        Bit('eNBID', BitLen=20, Repr='hex'),
        Bit('pad', BitLen=4, Repr='hex'),
    ]
Ejemplo n.º 5
0
 def init_idents(self,
                 ind_gummei=None,
                 ind_plmn=None,
                 ind_gid=None,
                 ind_mmec=None):
     #
     # initializing indexes for the MMEd instance
     if ind_gummei is not None and ind_gummei != self._ind_gummei:
         self._ind_gummei = ind_gummei
         self._log('WNG', 'changing MME GUMMEI index')
     if ind_plmn is not None and ind_plmn != self._ind_plmn:
         self._ind_plmn = ind_plmn
         self._log('WNG', 'changing MME PLMN index')
     if ind_gid is not None and ind_gid != self._ind_gid:
         self._ind_gid = ind_gid
         self._log('WNG', 'changing MME  index')
     if ind_mmec is not None and ind_mmec != self._ind_mmec:
         self._ind_mmec = ind_mmec
         self._log('WNG', 'changing MME  index')
     #
     # selecting the right identifier indexed and setting the right value
     # for the MMEd instance
     if self._ind_gummei >= len(self.ConfigS1['ServedGUMMEIs']):
         gummei = self.ConfigS1['ServedGUMMEIs'][0]
         self._log('WNG', 'invalid MME GUMMEI index')
     else:
         gummei = self.ConfigS1['ServedGUMMEIs'][self._ind_gummei]
     #
     if self._ind_plmn >= len(gummei['servedPLMNs']):
         self.MME_PLMN_BUF = gummei['servedPLMNs'][0]
         self._log('WNG', 'invalid MME PLMN index')
     else:
         self.MME_PLMN_BUF = gummei['servedPLMNs'][self._ind_plmn]
     plmn = PLMN()
     plmn.map(self.MME_PLMN_BUF)
     self.MME_PLMN = plmn.get_mccmnc()
     #
     if self._ind_gid >= len(gummei['servedGroupIDs']):
         self.MME_GID_BUF = gummei['servedGroupIDs'][0]
         self._log('WNG', 'invalid MME Group ID index')
     else:
         self.MME_GID_BUF = gummei['servedGroupIDs'][self._ind_gid]
     self.MME_GID = unpack('!H', self.MME_GID_BUF)[0]
     #
     if self._ind_mmec >= len(gummei['servedMMECs']):
         self.MME_MMEC_BUF = gummei['servedMMECs'][0]
         self._log('WNG', 'invalid MME Group ID index')
     else:
         self.MME_MMEC_BUF = gummei['servedMMECs'][self._ind_mmec]
     self.MME_MMEC = ord(self.MME_MMEC_BUF)
     #
     self.MME_GUMMEI = '{0}.{1}.{2}.{3}'.format(plmn.get_mcc(),
                                                plmn.get_mnc(),
                                                hexlify(self.MME_GID_BUF),
                                                hexlify(self.MME_MMEC_BUF))
Ejemplo n.º 6
0
Archivo: MME.py Proyecto: ewa/libmich
 def init_idents(self, ind_gummei=None, ind_plmn=None, ind_gid=None, ind_mmec=None):
     #
     # initializing indexes for the MMEd instance
     if ind_gummei is not None and ind_gummei != self._ind_gummei:
         self._ind_gummei = ind_gummei
         self._log('WNG', 'changing MME GUMMEI index')
     if ind_plmn is not None and ind_plmn != self._ind_plmn:
         self._ind_plmn = ind_plmn
         self._log('WNG', 'changing MME PLMN index')
     if ind_gid is not None and ind_gid != self._ind_gid:
         self._ind_gid = ind_gid
         self._log('WNG', 'changing MME  index')
     if ind_mmec is not None and ind_mmec != self._ind_mmec:
         self._ind_mmec = ind_mmec
         self._log('WNG', 'changing MME  index')
     #
     # selecting the right identifier indexed and setting the right value for the MMEd instance
     if self._ind_gummei >= len(self.ConfigS1['ServedGUMMEIs']):
         gummei = self.ConfigS1['ServedGUMMEIs'][0]
         self._log('WNG', 'invalid MME GUMMEI index')
     else:
         gummei = self.ConfigS1['ServedGUMMEIs'][self._ind_gummei]
     #
     if self._ind_plmn >= len(gummei['servedPLMNs']):
         self.MME_PLMN_BUF = gummei['servedPLMNs'][0]
         self._log('WNG', 'invalid MME PLMN index')
     else:
         self.MME_PLMN_BUF = gummei['servedPLMNs'][self._ind_plmn]
     plmn = PLMN()
     plmn.map(self.MME_PLMN_BUF)
     self.MME_PLMN = plmn.get_mccmnc()
     #
     if self._ind_gid >= len(gummei['servedGroupIDs']):
         self.MME_GID_BUF = gummei['servedGroupIDs'][0]
         self._log('WNG', 'invalid MME Group ID index')
     else:
         self.MME_GID_BUF = gummei['servedGroupIDs'][self._ind_gid]
     self.MME_GID = unpack('!H', self.MME_GID_BUF)[0]
     #
     if self._ind_mmec >= len(gummei['servedMMECs']):
         self.MME_MMEC_BUF = gummei['servedMMECs'][0]
         self._log('WNG', 'invalid MME Group ID index')
     else:
         self.MME_MMEC_BUF = gummei['servedMMECs'][self._ind_mmec]
     self.MME_MMEC = ord(self.MME_MMEC_BUF)
     #
     self.MME_GUMMEI = '{0}.{1}.{2}.{3}'.format(plmn.get_mcc(),
                                                plmn.get_mnc(),
                                                hexlify(self.MME_GID_BUF),
                                                hexlify(self.MME_MMEC_BUF))
Ejemplo n.º 7
0
def convert_eutran_cgi(eutran_cgi):
    plmn = PLMN()
    plmn.map(eutran_cgi['pLMNidentity'])
    return (plmn, '%.7x' % eutran_cgi['cell-ID'][0])
Ejemplo n.º 8
0
def convert_tai(tai):
    plmn = PLMN()
    plmn.map(tai['pLMNidentity'])
    return (plmn, unpack('>H', tai['tAC'])[0])
Ejemplo n.º 9
0
def convert_eutran_cgi(eutran_cgi):
    plmn = PLMN()
    plmn.map(eutran_cgi['pLMNidentity'])
    return (plmn, '%.7x' % eutran_cgi['cell-ID'][0])
Ejemplo n.º 10
0
def convert_tai(tai):
    plmn = PLMN()
    plmn.map(tai['pLMNidentity'])
    return (plmn, unpack('>H', tai['tAC'])[0])
Ejemplo n.º 11
0
class MMEd(object):
    #
    #-------------------------#
    # debug and tracing level #
    #-------------------------#
    #
    # verbosity level: list of log types to display when calling self._log(logtype, msg)
    DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
    # for logging SCTP socket send() / recv() content
    TRACE_SK = True
    # for logging ASN.1 PDU decoded / encoded
    TRACE_ASN1 = True
    # for logging NAS security headers decoded / encoded
    TRACE_SEC = True
    # for logging NAS PDU decoded / encoded
    TRACE_NAS = True
    # for logging SMS-CP PDU decoded / encoded
    TRACE_SMS = True
    #
    #---------------------#
    # MME server settings #
    #---------------------#
    #
    # those parameters are configured during self.init_server(ip, port)
    # IP version
    #SERVER_IPVERS = 4
    # IP address to listen to
    #SERVER_IP = ''
    # SCTP port to listen to
    #SERVER_PORT = 36412
    #
    # SCTP socket recv() buffer length
    SERVER_BUFLEN = 2048
    # SCTP server maximum client (S1AP instance -> eNodeB)
    SERVER_MAXCLI = 16
    #
    # MME scheduler resolution:
    # This is the resolution (in second) for the MME to check the list of registered UE,
    # and check for NAS procedures in timeout.
    # This is also applied as a timeout on the main select() loop.
    SCHED_RES = 0.1
    # In case we want to disable cleaning NAS stacks on timeout, set to False
    SCHED_UE_TO = True
    #
    #------------------------#
    # LTE Services referrers #
    #------------------------#
    # These are references to services handlers
    # setup at MMEd initialization
    #
    # Authentication Centre handler
    AUCd = None
    # GPRS tunneling protocol handler
    GTPd = None
    # SMS center handler
    SMSd = None
    # LTE Positionning handler
    LPPd = None
    #
    #-----------------#
    # UE and ENB dict #
    #-----------------#
    # These are tables referencing allowed (and connected) UEs and eNBs
    # setup at MME initialization through config file
    #
    # UE references UEd instances, indexed by IMSI (str)
    #UE = {}
    # UE config for storing IP addresses (at least): imsi: {'IP':@IP}
    #UEConfig = {}
    # when an unknown UE gets connected, a default IP address is configured for it
    UE_UNKNOWN_IP = '192.168.100.250'
    # when an unknown UE gets connected, an ID type (1: IMSI) has to be requested
    UE_UNKNOWN_IDTYPE = [1]
    #
    # ENB references ENBd instances, indexed by eNB Global ID (tuple: PLMNIdentity (str), eNB-ID (hex-str))
    #ENB = {}
    # ENB config for whitelisting all eNB Global ID allowed
    # if empty, any eNodeB which advertises our MME PLMN in its Global ID will be allwoed
    #ENBConfig = {}
    #
    #-------------------------#
    # MME specific parameters #
    #-------------------------#
    # Parameters used for S1 link setup
    ConfigS1 = {
        'MMEname':
        'MichTelecomMME',  # optional
        'ServedGUMMEIs': [{
            'servedPLMNs': [bytes(PLMN('00101'))],
            'servedGroupIDs': ['\x00\x01'],
            'servedMMECs': ['\x01']
        }],  # mandatory, at least 1
        'RelativeMMECapacity':
        20,  # mandatory, 0 < X < 255
        'MMERelaySupportIndicator':
        None,  # optional
        'CriticalDiagnostics':
        None,  # optional, 'true' otherwise
    }
    # those indexes are used to build the MME main GUMMEI and {PLMN, GID, MMEC}
    _ind_gummei = 0
    _ind_plmn = 0
    _ind_gid = 0
    _ind_mmec = 0

    #
    # after instantiation, the following MME identifiers are available:
    #MME_GUMMEI, MME_PLMN_BUF, MME_PLMN, MME_GID_BUF, MME_GID, MME_MMEC_BUF, MME_MMEC

    def _log(self, logtype='DBG', msg=''):
        #
        # logtype: 'ERR', 'WNG', 'INF', 'DBG'
        # specials: 'TRACE_SK', 'TRACE_ASN1', 'TRACE_SEC', 'TRACE_NAS', 'TRACE_SMS'
        #
        if logtype[:6] == 'TRACE_':
            tracetype = logtype[6:]
            if tracetype[:2] == 'SK' and self.TRACE_SK:
                log('[{0}] [MME: {1}] {2}'.format(logtype, self.MME_GUMMEI,
                                                  hexlify(msg)))
            elif (tracetype[:4] == 'ASN1' and self.TRACE_ASN1) \
              or (tracetype[:3] == 'SEC' and self.TRACE_SEC) \
              or (tracetype[:3] == 'NAS' and self.TRACE_NAS) \
              or (tracetype[:3] == 'SMS' and self.TRACE_SMS) :
                hdr, cont = msg.split('\n', 1)
                log('[{0}] [MME: {1}] {2}\n{3}{4}{5}'\
                    .format(logtype, self.MME_GUMMEI, hdr, TRA_COLOR_START, cont, TRA_COLOR_END))
        elif logtype in self.DEBUG:
            log('[{0}] [MME: {1}] {2}'.format(logtype, self.MME_GUMMEI, msg))

    #--------------------#
    # MME initialization #
    #--------------------#

    def __init__(self, config={'server': ('127.0.1.100', 36412)}):
        #
        # config: dict with keys:
        #   'server', 'ue', 'enb'
        # server: network IP / port tuple
        #   (IP (str), sctp_port (int))
        # ue: dict with keys:
        #   '$imsi':(gtp_ip (str), ...)
        # enb: tuple of enb_global_id
        #   ($enb_global_id (tuple(str, int)), ...)
        #
        # create un uncorrelated copy of the class Config dict
        self.ConfigS1 = cpdict(self.__class__.ConfigS1)
        #
        # initialize MME GUMMEI (required for ._log())
        self.init_idents()
        #
        # initialize MME config
        if 'server' in config:
            self.init_server(config['server'][0], config['server'][1])
        #
        self.UE = {}
        self.UEConfig = {}
        # for joining UE though their network identities (IP@, phone num for SMS)
        self.IP_IMSI = {}
        self.Num_IMSI = {}
        if 'ue' in config:
            for imsi in config['ue']:
                self.init_ue(imsi, **config['ue'][imsi])
        self._log('DBG',
                  'UE IMSI configured: {0}'.format(self.UEConfig.keys()))
        # for all MME UE S1AP ID (mme_ue_id: imsi)
        self.UE_MME_ID = {}
        self._ue_mme_id = 0
        self._ue_delayed = {}
        # for all randomly attributed TMSI
        self.TMSI = {}
        #
        self.ENB = {}
        self.ENBConfig = {}
        if 'enb' in config:
            for enb_gid in config['enb']:
                self.init_enb(enb_gid, **config['enb'][enb_id])
        self._log('DBG',
                  'eNodeB Global ID configured: {0}'.format(self.ENB.keys()))
        # for all SCTP sockets established for each eNB (sk:enb_gid)
        self.ENBSk = {}
        # for all Tracking Areas handled by attached eNB (ta:[enb_gid, ...])
        self.TA = {}
        # for keeping a cache of all randomly attributed downlink TEID
        self._TEID = []
        #
        # S1AP PDU message ASN.1 PER aligned encoder / decoder
        self._S1AP_PDU = GLOBAL.TYPE['S1AP-PDU']
        #
        # random generator for TMSI
        self._rand = SystemRandom()
        #
        self._running = False
        self._bg = threadit(self.start)

    def init_idents(self,
                    ind_gummei=None,
                    ind_plmn=None,
                    ind_gid=None,
                    ind_mmec=None):
        #
        # initializing indexes for the MMEd instance
        if ind_gummei is not None and ind_gummei != self._ind_gummei:
            self._ind_gummei = ind_gummei
            self._log('WNG', 'changing MME GUMMEI index')
        if ind_plmn is not None and ind_plmn != self._ind_plmn:
            self._ind_plmn = ind_plmn
            self._log('WNG', 'changing MME PLMN index')
        if ind_gid is not None and ind_gid != self._ind_gid:
            self._ind_gid = ind_gid
            self._log('WNG', 'changing MME  index')
        if ind_mmec is not None and ind_mmec != self._ind_mmec:
            self._ind_mmec = ind_mmec
            self._log('WNG', 'changing MME  index')
        #
        # selecting the right identifier indexed and setting the right value
        # for the MMEd instance
        if self._ind_gummei >= len(self.ConfigS1['ServedGUMMEIs']):
            gummei = self.ConfigS1['ServedGUMMEIs'][0]
            self._log('WNG', 'invalid MME GUMMEI index')
        else:
            gummei = self.ConfigS1['ServedGUMMEIs'][self._ind_gummei]
        #
        if self._ind_plmn >= len(gummei['servedPLMNs']):
            self.MME_PLMN_BUF = gummei['servedPLMNs'][0]
            self._log('WNG', 'invalid MME PLMN index')
        else:
            self.MME_PLMN_BUF = gummei['servedPLMNs'][self._ind_plmn]
        plmn = PLMN()
        plmn.map(self.MME_PLMN_BUF)
        self.MME_PLMN = plmn.get_mccmnc()
        #
        if self._ind_gid >= len(gummei['servedGroupIDs']):
            self.MME_GID_BUF = gummei['servedGroupIDs'][0]
            self._log('WNG', 'invalid MME Group ID index')
        else:
            self.MME_GID_BUF = gummei['servedGroupIDs'][self._ind_gid]
        self.MME_GID = unpack('!H', self.MME_GID_BUF)[0]
        #
        if self._ind_mmec >= len(gummei['servedMMECs']):
            self.MME_MMEC_BUF = gummei['servedMMECs'][0]
            self._log('WNG', 'invalid MME Group ID index')
        else:
            self.MME_MMEC_BUF = gummei['servedMMECs'][self._ind_mmec]
        self.MME_MMEC = ord(self.MME_MMEC_BUF)
        #
        self.MME_GUMMEI = '{0}.{1}.{2}.{3}'.format(plmn.get_mcc(),
                                                   plmn.get_mnc(),
                                                   hexlify(self.MME_GID_BUF),
                                                   hexlify(self.MME_MMEC_BUF))

    def init_server(self, ip=None, port=None):
        '''
        Initialize the SCTP server of the MME.
        The server socket is made available through the *_sk* attribute.
        
        Kwargs:
            ip (str): IP address (V4 or V6 or wildcard), default to ''
            port (int): SCTP port, default to 36412
        
        Returns:
            None
        
        Raises:
            MMEErr
        '''
        # default settings are set as class attributes
        #
        # IP addr
        if ip.count('.') == 3:
            self.SERVER_IPVERS = 4
            self.SERVER_IP = ip
        elif ip.count(':') > 0:
            self.SERVER_IPVERS = 6
            self.SERVER_IP = ip
        #
        # SCTP port
        if 0 < port < 65536:
            self.SERVER_PORT = port
        #
        # TODO: support SCTP multihoming / multi-addresses
        self.SERVER_ADDR = (self.SERVER_IP, self.SERVER_PORT)
        #
        # start SCTP server
        try:
            if self.SERVER_IPVERS == 6:
                self._sk = sctpsocket_tcp(socket.AF_INET6)
            else:
                self._sk = sctpsocket_tcp(socket.AF_INET)
            # S1AP uses SCTP adaptation layer 18
            self._sk.set_adaptation(18)
        except:
            raise (MMEErr('cannot create SCTP socket'))
        try:
            self._sk.bind(self.SERVER_ADDR)
        except:
            raise(MMEErr('cannot bind SCTP socket on address {0}'\
                  .format(self.SERVER_ADDR)))
        #
        self._log('DBG', 'SCTP server started on address {0}'\
                  .format(self.SERVER_ADDR))

    def init_ue(self, imsi, **kwargs):
        # kwargs keys: 'IP'
        self.UEConfig[imsi] = {}
        if 'IP' in kwargs:
            self.UEConfig[imsi]['IP'] = kwargs['IP']
            self.IP_IMSI[kwargs['IP']] = imsi
        else:
            self.UEConfig[imsi]['IP'] = None
            self._log('WNG',
                      'missing GTP IP address for IMSI {0}'.format(imsi))
        if 'Num' in kwargs:
            self.UEConfig[imsi]['Num'] = kwargs['Num']
            self.Num_IMSI[kwargs['Num']] = imsi
        else:
            self.UEConfig[imsi]['Num'] = None
            self._log('WNG', 'missing phone number for IMSI {0}'.format(imsi))

    def init_enb(self, enb_gid, **kwargs):
        # kwargs keys: None, yet
        self.ENBConfig[enb_gid] = {}

    #-----------#
    # randomly  #
    # generated #
    # tokens    #
    #-----------#

    def get_new_ue_mme_id(self):
        self._ue_mme_id += 1
        if self._ue_mme_id >= 4294967295:
            self._ue_mme_id = 1
        return self._ue_mme_id

    def get_new_tmsi(self):
        # TMSI is provided as type: bytes
        v = pack('!I', self._rand.randint(0, 4294967295))
        while v in self.TMSI:
            # this is not efficient, but should not happen that often !
            v = pack('!I', self._rand.randint(0, 4294967295))
        return v

    def get_new_teid(self):
        # TEID is provided as type: int
        v = self._rand.randint(0, 4294967295)
        while v in self._TEID:
            v = self._rand.randint(0, 4294967295)
        return v

    def get_sgw_addr(self):
        # return the GTPd internal IP for S1AP transportLayerAddress
        return self.GTPd.INT_IP

    #---------------------#
    # SCTP server runtime #
    #---------------------#
    #
    # The self.start() method with the main loop can be launched in a background thread
    # It's then possible to stop it with the .stop() method
    #
    def start(self):
        try:
            self._sk.listen(self.SERVER_MAXCLI)
        except:
            self.init_server()
            self._sk.listen(self.SERVER_MAXCLI)
        #
        self._running = True
        self._log('INF', 'SCTP server listening on address {0}'\
                  .format(self.SERVER_ADDR))
        #
        # main MME infinite loop using the socket select() call:
        # the loop gets new SCTP stream,
        # gets new SCTP messages for existing SCTP stream,
        # and eventually timeouts running procedures in case no messages are received
        T_lto = time()
        while self._running:
            sk_ready = []
            try:
                sk_ready = select([self._sk] + self.ENBSk.keys(), [], [],
                                  self.SCHED_RES)[0]
            except Exception as err:
                self._log('ERR',
                          'select() failed with Exception: {0}'.format(err))
                self._running = False
            #
            for sk in sk_ready:
                if sk is self._sk:
                    # read from server socket for a new STCP stream (S1SetupRequest)
                    self.handle_stream_new()
                else:
                    # read from established eNB SCTP socket for new message
                    # (whatever S1AP PDU)
                    self.handle_stream_msg(sk)
            #
            # clean-up potential signalling procedures in timeout
            if self.SCHED_UE_TO:
                if not len(sk_ready) or time() - T_lto > self.SCHED_RES:
                    # select() timeout or more than SCHED_RES since last timeout
                    self.handle_ue_timeout()
                    T_lto = time()
        #
        # server stopped
        self._log('INF', 'SCTP server stopped')

    def stop(self):
        # TODO: check when to cleanup ENBd and UEd instances
        #
        # stop the socket server
        if self._running:
            self._running = False
            sleep(self.SCHED_RES)
            for enb_gid in self.ENB:
                self.del_enb(enb_gid)
            # close the main SCTP listener
            self._sk.close()

    def add_enb(self, enb_gid):
        if enb_gid not in self.ENB:
            return
        enb = self.ENB[enb_gid]
        # update the list of Tracking Area supported by the MME
        if 'SupportedTAs' in enb.Config:
            for ta in enb.Config['SupportedTAs']:
                if ta['tAC'] not in self.TA:
                    self.TA[ta['tAC']] = set([enb_gid])
                else:
                    self.TA[ta['tAC']].add(enb_gid)
        # add the eNB sk to the socket list of the MME
        if enb.SK not in self.ENBSk:
            self.ENBSk[enb.SK] = enb_gid

    def del_enb(self, enb_gid):
        if enb_gid not in self.ENB:
            return
        enb = self.ENB[enb_gid]
        # close the SCTP socket
        sk = enb.SK
        if sk is not None:
            sk.close()
        enb.set_sk(None)
        # remove the eNB from the TA list of the MME
        if 'SupportedTAs' in enb.Config:
            for ta in enb.Config['SupportedTAs']:
                if ta['tAC'] in self.TA:
                    try:
                        self.TA[ta['tAC']].remove(enb_gid)
                    except:
                        pass
        # remove the eNB sk from the socket list of the MME
        if sk in self.ENBSk:
            del self.ENBSk[sk]
        # remove S1 info from UE previously attached to this eNB
        for ue in self.UE.values():
            if ue.ENB is not None and ue.ENB.GID == enb_gid:
                ue.s1_unset()

    #----------------------#
    # S1AP streams handler #
    #----------------------#

    def handle_stream_recv(self, sk):
        # remind the eNB behind the socket
        if sk in self.ENBSk:
            enb_gid = self.ENBSk[sk]
        else:
            enb_gid = ''
        # read the STCP message content
        buf = bytes()
        buf = sk.recv(self.SERVER_BUFLEN)
        # sk.sctp_recv() requires patching sctp_recv_msg() in pysctp/_sctp.c
        # moreover, it looks like the sinfo struct with SCTP stream id
        # and other metadata is empty in any way...
        #addr, flags, buf, notif = sk.sctp_recv(self.SERVER_BUFLEN)
        if not buf and enb_gid:
            # this is actually an eNB closing its STCP stream
            self.del_enb(enb_gid)
            self._log('DBG',
                      '[eNB: {0}] S1AP stream closed by eNB'.format(enb_gid))
            return
        self._log('TRACE_SK_UL', buf)
        #
        try:
            self._S1AP_PDU.decode(buf)
        except:
            self.send_enb_err(sk, cause=('protocol', 'transfer-syntax-error'))
            self._log('WNG', '[eNB: {0}] closing S1AP stream, S1AP PDU decoding error: {0}'\
                      .format(enb_gid, hexlify(buf)))
            self.del_enb(enb_gid)
            return
        #
        self._log(
            'TRACE_ASN1_UL',
            '[eNB: {0}]\n{1}'.format(enb_gid, self._S1AP_PDU._msg.show()))
        # return the S1AP PDU content values
        return self._S1AP_PDU()

    def handle_stream_new(self):
        sk, addr = self._sk.accept()
        self._log('DBG',
                  'new S1AP stream client from address {0}'.format(addr))
        #
        # get the S1AP PDU content values
        pdu = self.handle_stream_recv(sk)
        if pdu is None:
            sk.close()
            return
        #
        # Any new S1AP stream must start with a proper S1Setup procedure
        # TS 36.413, 8.7.3.1
        if pdu[0] == 'initiatingMessage' and pdu[1]['procedureCode'] == 17:
            self.handle_s1setup(sk, pdu)
        #
        else:
            self.send_enb_err(
                sk,
                cause=('protocol',
                       'message-not-compatible-with-receiver-state'))
            self._log('WNG', 'new S1AP stream without S1Setup (type {0}, code {1}), closing S1AP stream'\
                      .format(pdu[0], pdu[1]['procedureCode']))
            sk.close()

    def handle_s1setup(self, sk, pdu):
        # get protocolIEs' values
        protIEs = pdu[1]['value'][1]['protocolIEs']
        # get the eNB Global ID
        plmn_id, enb_id = None, None
        for protIE in protIEs:
            if protIE['id'] == 59:
                enb_id_bitstr = protIE['value'][1]['eNB-ID'][1]
                plmn_id_octstr = protIE['value'][1]['pLMNidentity']
                # convert to readable values:
                # 1) enb id
                if enb_id_bitstr[1] == 20:
                    # macroENB-ID
                    enb_id = '%.5x' % enb_id_bitstr[0]
                elif enb_id_bitstr[1] == 28:
                    # homeENB-ID
                    enb_id = '%.7x' % enb_id_bitstr[0]
                # 2) plmn id
                plmn_id = PLMN()
                plmn_id.map(plmn_id_octstr)
                plmn_id = plmn_id.get_mccmnc()
        #
        if plmn_id and enb_id:
            enb_gid = (plmn_id, enb_id)
            # create / reset the eNB handler instance
            ret = self.create_enb_handler(enb_gid, sk)
            if not ret:
                # eNB not allowed
                self.send_enb_err(sk, cause=('misc', 'unknown-PLMN'))
                self._log('WNG', '[eNB: {0}] eNB not allowed, closing S1AP stream'\
                          .format(enb_gid))
                sk.close()
                return
            #
            self._log('INF',
                      '[eNB: {0}] S1AP stream established'.format(enb_gid))
            # process the PDU in the context of the eNB handler
            #'''
            try:
                ret_pdu = self.ENB[enb_gid].process_pdu(pdu)
            except Exception as err:
                self._exc = sys.exc_info()
                self._log('ERR', 'something went wrong in the ENBmgr code: {0}'\
                          .format(err))
                print('ERROR: something went wrong in the ENBmgr code:\n{0}'\
                      .format(err))
                traceback.print_tb(self._exc[2])
            else:
                self.add_enb(enb_gid)
                for pdu in ret_pdu:
                    self.send_enb(sk, pdu)

    def create_enb_handler(self, enb_gid, sk):
        if len(self.ENBConfig):
            if enb_gid in self.ENBConfig:
                self._create_enb_handler(enb_gid, sk)
                return True
        # enb_gid = (plmn_id, enb_id)
        elif enb_gid[0] == self.MME_PLMN:
            self._create_enb_handler(enb_gid, sk)
            return True
        return False

    def _create_enb_handler(self, enb_gid, sk):
        if enb_gid in self.ENB:
            # eNB already attached the MME in the past
            self.ENB[enb_gid].reset()
            self.ENB[enb_gid].set_sk(sk)
        else:
            # new eNB
            self.ENB[enb_gid] = ENBd(enb_gid, self)
            self.ENB[enb_gid].set_sk(sk)

    def handle_stream_msg(self, sk):
        # get the S1AP PDU content values
        pdu = self.handle_stream_recv(sk)
        if pdu is None:
            return
        enb_gid = self.ENBSk[sk]
        #
        # check for ErrorInd related to a UE
        mme_ue_id, enb_ue_id = get_ue_s1ap_id(pdu)
        if pdu[1]['procedureCode'] == 15 and mme_ue_id in self.UE_MME_ID:
            imsi = self.UE_MME_ID[mme_ue_id]
            ue = self.UE[imsi]
            ret_pdu = ue.process(pdu)
            if ret_pdu:
                for pdu in ret_pdu:
                    self.send_enb(sk, pdu, uerel=True)
        #
        # check if it is an eNB-related PDU
        elif pdu[1]['procedureCode'] in S1APENBProcCodes:
            enb = self.ENB[enb_gid]
            #'''
            try:
                ret_pdu = enb.process_pdu(pdu)
            except Exception as err:
                self._exc = sys.exc_info()
                self._log('ERR', 'something went wrong in the ENBmgr code: {0}'\
                          .format(err))
                print('ERROR: something went wrong in the ENBmgr code:\n{0}'\
                      .format(err))
                traceback.print_tb(self._exc[2])
            else:
                if ret_pdu:
                    for pdu in ret_pdu:
                        self.send_enb(sk, pdu)
        #
        # check if it is a UE-related PDU
        elif pdu[1][
                'procedureCode'] in S1APUEProcCodes and enb_ue_id is not None:
            # decode potential NAS-PDU
            #self.ue_decode_nas(pdu)
            # check if it is a UE newly attaching to the eNB
            if mme_ue_id is None:
                # get a new MME UE ID
                mme_ue_id = self.get_new_ue_mme_id()
                # try to retrieve the IMSI for the UE
                imsi = self.ue_get_imsi(pdu)
                # if not possible, start a special procedure to request the IMSI
                # and delay the processing of the pdu
                if imsi is None:
                    self._ue_delayed[mme_ue_id] = pdu
                    for req in self.ue_request_ident(enb_gid, mme_ue_id,
                                                     enb_ue_id):
                        self.send_enb(sk, req, uerel=True)
                # otherwise, just setup the UE handler in the MME registries
                # and pass it the S1AP PDU to process
                else:
                    self.handle_ue_stream_msg(sk,
                                              enb_gid,
                                              imsi,
                                              pdu,
                                              initial=True,
                                              ue_id=(mme_ue_id, enb_ue_id))
            #
            elif mme_ue_id in self._ue_delayed:
                # we should get an EPS IDENTITY RESPONSE with IMSI here
                imsi = self.ue_retrieve_imsi(pdu, enb_gid)
                if imsi:
                    # setup the UE handler in the MME registries
                    # and pass it the delayed S1AP PDU to process
                    self.handle_ue_stream_msg(sk,
                                              enb_gid,
                                              imsi,
                                              self._ue_delayed[mme_ue_id],
                                              initial=True,
                                              ue_id=(mme_ue_id, enb_ue_id))
                del self._ue_delayed[mme_ue_id]
            #
            elif mme_ue_id in self.UE_MME_ID:
                imsi = self.UE_MME_ID[mme_ue_id]
                self.handle_ue_stream_msg(sk, enb_gid, imsi, pdu)
            #
            else:
                # unknown S1AP MME UE ID: error
                self.send_enb_err(sk,
                                  cause=('radioNetwork',
                                         'unknown-mme-ue-s1ap-id'))
                self._log('WNG', '[eNB: {0}] unknown MME-UE-S1AP-ID {1}'\
                          .format(enb_gid, mme_ue_id))
                return
        #
        else:
            # invalid S1AP message
            self.send_enb_err(sk, cause=('protocol', 'semantic-error'))
            self._log('WNG', '[eNB: {0}] unknown S1AP procedureCode {1}'\
                      .format(enb_gid, pdu[1]['procedureCode']))

    def handle_ue_stream_msg(self,
                             sk,
                             enb_gid,
                             imsi,
                             pdu,
                             initial=False,
                             ue_id=None):
        ue = self.ue_get_handler(imsi)
        if initial:
            # InitialUEMessage
            self.UE_MME_ID[ue_id[0]] = imsi
            ue.s1_set(enb_gid, ue_id[0], ue_id[1])
        #
        try:
            ret_pdu = ue.process_pdu(pdu)
        except Exception as err:
            self._exc = sys.exc_info()
            self._log(
                'ERR',
                'something went wrong in the UEmgr code: {0}'.format(err))
            print('ERROR: something went wrong in the UEmgr code:\n{0}'.format(
                err))
            traceback.print_tb(self._exc[2])
        else:
            if ret_pdu:
                for pdu in ret_pdu:
                    self.send_enb(sk, pdu, uerel=True)

    def send_enb(self, sk, pdu, uerel=False):
        # remind the eNB behind the socket
        if sk in self.ENBSk:
            enb_gid = self.ENBSk[sk]
        else:
            enb_gid = ''
        # send the encoded S1AP-PDU to the eNB
        try:
            self._S1AP_PDU.encode(pdu)
        except Exception as err:
            self._log('ERR', '[eNB: {0}] S1AP PDU encoding error: {1}'\
                      .format(enb_gid, err))
            self._S1AP_ENC_ERR = pdu
        else:
            self._log('TRACE_ASN1_DL', '[eNB: {0}]\n{1}'\
                      .format(enb_gid, self._S1AP_PDU._msg.show()))
            # send the buffer over the SCTP socket
            buf = bytes(self._S1AP_PDU)
            if uerel:
                stream_id = 1
            else:
                stream_id = 0
            try:
                sk.sctp_send(buf, ppid=socket.htonl(18), stream=stream_id)
            except Exception as err:
                self._log('ERR', '[eNB: {0}] unable to send SCTP message, exception: {1}'\
                          .format(enb_gid, err))
            else:
                self._log('TRACE_SK_DL', buf)

    #--------------------#
    # eNB errors handler #
    #--------------------#

    def send_enb_err(self, sk, cause=None, diag=None):
        # send an S1AP Error Indication, not related to any UE,
        # when required early in the MME server process
        # Cause and CriticalityDiagnostics may be provided
        protIEs = []
        if cause is not None:
            protIEs.append({
                'value': ('Cause', cause),
                'id': 2,
                'criticality': 'ignore'
            })
        if diag is not None:
            protIEs.append({
                'value': ('CriticalityDiagnostics', diag),
                'id': 58,
                'criticality': 'ignore'
            })
        pdu_val = ('initiatingMessage', {
            'procedureCode': 15,
            'value': ('ErrorIndication', {
                'protocolIEs': protIEs
            }),
            'criticality': 'ignore'
        })
        self.send_enb(sk, pdu_val)

    #-------------#
    # UE routines #
    #-------------#

    def ue_decode_nas(self, pdu):
        if pdu[1]['procedureCode'] not in S1AP_NAS_OFFSET:
            return
        nas_ie = pdu[1]['value'][1]['protocolIEs'][S1AP_NAS_OFFSET[
            pdu[1]['procedureCode']]]
        if nas_ie['value'][0] == 'NAS-PDU':
            # OCTET STRING, to be parsed
            nas_ie['value'] = ('NAS-PDU', parse_L3(nas_ie['value'][1]))

    def ue_get_imsi(self, pdu):
        # collect S-TMSI and NAS-PDU in the S1AP PDU
        pIEs = pdu[1]['value'][1]['protocolIEs']
        for pIE in pIEs:
            if pIE['id'] == 26:
                # NAS-PDU
                nas = parse_L3(pIE['value'][1])
                tmsi = get_tmsi(nas)
                if tmsi is not None:
                    if tmsi in self.TMSI:
                        return self.TMSI[tmsi]
                else:
                    imsi = get_imsi(nas)
                    if imsi is not None:
                        return imsi
            elif pIE['id'] == 96:
                # S-TMSI, nly m-TMSI is processed, mMEC is ignored
                tmsi = pIE['value'][1]['m-TMSI']
                if tmsi in self.TMSI:
                    return self.TMSI[tmsi]
        return None

    def ue_request_ident(self, enb_gid, mme_ue_id, enb_ue_id):
        reqs = []
        for id_type in self.UE_UNKNOWN_IDTYPE:
            naspdu = EPS_IDENTITY_REQUEST(IDType=id_type)
            pIEs = [{
                'value': ('MME-UE-S1AP-ID', mme_ue_id),
                'criticality': 'ignore',
                'id': 0
            }, {
                'value': ('ENB-UE-S1AP-ID', enb_ue_id),
                'criticality': 'reject',
                'id': 8
            }, {
                'value': ('NAS-PDU', bytes(naspdu)),
                'criticality': 'reject',
                'id': 26
            }]
            #
            self._log('TRACE_NAS_DL', '[eNB: {0}] [UE: ]\n{1}'\
                      .format(enb_gid, naspdu.show()))
            reqs.append(('initiatingMessage', {
                'procedureCode':
                11,
                'value': ('DownlinkNASTransport', {
                    'protocolIEs': pIEs
                }),
                'criticality':
                'ignore'
            }))
        #
        return reqs

    def ue_retrieve_imsi(self, pdu, enb_gid):
        # retrieve the IMSI from the NAS-PDU response
        if pdu[1]['procedureCode'] not in S1AP_NAS_OFFSET:
            return
        nas_ie = pdu[1]['value'][1]['protocolIEs'][S1AP_NAS_OFFSET[
            pdu[1]['procedureCode']]]
        if nas_ie['value'][0] == 'NAS-PDU':
            naspdu = parse_L3(nas_ie['value'][1])
            self._log('TRACE_NAS_UL', '[eNB {0}] [UE: ]\n{1}'\
                      .format(enb_gid, naspdu.show()))
            try:
                ident = naspdu.ID.getobj()
            except:
                pass
            else:
                return ident.get_imsi()
        return None

    def ue_get_handler(self, imsi):
        if imsi in self.UE and self.UE[imsi] is not None:
            return self.UE[imsi]
        else:
            ue = UEd(imsi, self)
            if imsi in self.UEConfig:
                ue.init_ipaddr(self.UEConfig[imsi]['IP'])
            else:
                ue.init_ipaddr(self.UE_UNKNOWN_IP)
            self.UE[imsi] = ue
            return ue

    def handle_ue_timeout(self):
        T = time()
        for imsi in self.UE:
            ue = self.UE[imsi]
            if ue is not None:
                if ue.Proc['EMM']:
                    for emm_proc in reversed(ue.Proc['EMM']):
                        if hasattr(emm_proc,
                                   'TimerStop') and T > emm_proc.TimerStop:
                            emm_proc.timeout()
                if ue.Proc['ESM']:
                    for esm_proc in reversed(ue.Proc['ESM']):
                        if hasattr(esm_proc,
                                   'TimerStop') and T > esm_proc.TimerStop:
                            esm_proc.timeout()
                if ue.Proc['SMS']:
                    for sms_proc in ue.Proc['SMS'].values():
                        if hasattr(sms_proc,
                                   'TimerStop') and T > sms_proc.TimerStop:
                            sms_proc.timeout()