Skip to content

Working with GTPC

mitshell edited this page Feb 27, 2024 · 1 revision

How to encode and decode GTP signalling

Modules handling GTP

There are 4 modules for dealing with GTP, available in pycrate:

  • TS0960_GTPv0: it implements the GTP-C version 0 legacy protocol, in use in GPRS networks before 3G was deployed, defined in the 3GPP TS [09.60]((https://www.3gpp.org/DynaReport/0960.htm)
  • TS29060_GTP: it implements the GTP-C version 1 protocol, as used in 2G-3G networks and defined in the 3GPP TS 29.060 specification
  • TS29274_GTPC: it implements the GTP-C version 2 protocol, as used in 4G networks and defined in the 3GPP TS 29.274 specification
  • TS29281_GTPU: it implements the GTP-U protocol, as used in 2G-3G-4G-5G networks and defined in the 3GPP TS 29.281 specification

Additionally, the recent Packet Forwarding Control Protocol (PFCP) is implemented in the TS29244_PFCP module, what corresponds to the specification TS 29.244. It is used to control UPF in 5G networks, and eventually in some 4G networks too. GTP-C v2 and PFCP are very similar in terms of message structure. Therefore some constructs are shared between the two modules.

Generally, GTPv0 and GTPv1 are used between SGSN and GGSN, while GTPv2 is used between MME, SGW and PGW. For the cellular User-Plane encapsulation, GTP-U is used accross many equipments from 2G to 5G networks: SGSN-GGSN in 2G, but also RNC-SGSN-GGSN in 3G, eNodeB-SGW-PGW in 4G and gNodeB-UPF in 5G. Those protocols, especially the most recent versions, can also be used on some other interfaces, as visible in the GTPv2-C Type dictionnary.

Basically, a GTP-C or PFCP message is made of a header and a sequence of information elements; some are mandatory, others are optional (or conditional). Each information element is structured in a Type-Value (in GTP-C v1 only) or Type-Length-Value fashion, with some subtleties:

  • The Type is originally coded on 8 bits but a mechanism exists to extend it to 16 bits (except in PFCP, where it is always on 16 bits).
  • In GTP-C v0 and v1, the order of IEs is fixed, and several IEs of the same type can appear sequentially in a message.
  • In GTP-C v2 and PFCP, the order of IEs is not fixed. In GTP-C v2, an Instance indication is added to the Type of each IE, so that several IEs of the same type can be distinctively used in a message.
  • In GTP-C v2 and PFCP, grouped IEs can be set within an IE, leading to nested structures. In GTP-C v2, those grouped IEs have a locally-defined IE's Type.

To help with visualizing GTP-C message structure, a tool is provided as part of pycrate: pycrate_gtp_type_info.py. It can help with creating appropriate values for the several GTP-C message classes.

Decoding GTP-C v2 messages

In order to decode GTP-C v2 messages, the easiest is to use the main parsing function parse_GTPC() from module TS29274_GTPC, which takes a buffer and returns a 2-tuple: the parsed message structure (or None if the parsing failed completely) and an error code (0 if everything went fine, or a code which is common to the 3 control protocols: GTP-C v1, GTP-C v2 and PFCP).

The possible error codes are the following:

  • ERR_PFCP_BUF_TOO_SHORT: 1
  • ERR_PFCP_BUF_INVALID: 2
  • ERR_PFCP_TYPE_NONEXIST: 3
  • ERR_PFCP_MAND_IE_MISS: 4
In [1]: from pycrate_mobile.TS29274_GTPC import *                                                                                                        

In [2]: from binascii import * 

In [3]: buf  = unhexlify('482000ec0012345601e240000100080000010189674523f14c00050011223344554b000800112233445566778856000d001800f110123400f11009988770530
    ...: 0030000f1105200010006570009008612345678ac141e2847002300066d792d61706e08746573742d6d6e6f066d6e63303031066d6363303031046770727380000100016300010001
    ...: 4f00050001000000007f000100004800080000100000001000004e001a008080211001000010810600000000830600000000000d00000a005d002c004900010005570009028444554
    ...: 455ac5a5046500016007c090000000000000000000000000000000000000000')                                                                                

In [4]: Msg, Err = parse_GTPC(buf)                                                                                                                       

In [5]: Err # no error while decoding the buffer
Out[5]: 0

In [6]: print(Msg.show())                                                                                                                                
### CreateSessionReq ###
 ### GTPCHdr ###
  <Version : 2>
  <P : 0>
  <T : 1>
  <MP : 0>
  <spare : 0>
  <Type : 32 (Create Session Request)>
  <Len : 236>
  <TEID : 0x00123456>
  <SeqNum : 123456>
  <spare : 0>
     ### CreateSessionReqIEs ###
      ### IMSI ###
       ### Hdr ###
        <Type : 1 (International Mobile Subscriber Identity (IMSI))>
        <Len : 8>
        <spare : 0>
        <Inst : 0>
           <Data : 001010987654321>
      ### MSISDN ###
       ### Hdr ###
        <Type : 76 (MSISDN)>
        <Len : 5>
        <spare : 0>
        <Inst : 0>
           <Data : 1122334455>
      ### MEI ###
       ### Hdr ###
        <Type : 75 (Mobile Equipment Identity (MEI))>
        <Len : 8>
        <spare : 0>
        <Inst : 0>
           <Data : 1122334455667788>
      ### ULI ###
       ### Hdr ###
        <Type : 86 (User Location Information (ULI))>
        <Len : 13>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            ### Flags ###
             <ExtMacroENBID : 0>
             <MacroENBID : 0>
             <LAI : 0>
             <ECGI : 1>
             <TAI : 1>
             <RAI : 0>
             <SAI : 0>
             <CGI : 0>
            ### TAI ###
             <PLMN : 00101>
             <TAC : 0x1234>
            ### ECGI ###
             <PLMN : 00101>
             <spare : 0x0>
             <ECI : 0x9988770>
            <ext : 0x>
      ### ServingNetwork ###
       ### Hdr ###
        <Type : 83 (Serving Network)>
        <Len : 3>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <PLMN : 00101>
            <ext : 0x>
      ### RATType ###
       ### Hdr ###
        <Type : 82 (RAT Type)>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <Val : 6 (EUTRAN (WB-E-UTRAN))>
            <ext : 0x>
      ### SenderFTEIDforControlPlane ###
       ### Hdr ###
        <Type : 87 (Fully Qualified Tunnel Endpoint Identifier (F-TEID))>
        <Len : 9>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <V4 : 1>
            <V6 : 0>
            <IF : 6 (S5/S8 SGW GTP-C interface)>
            <TEID_GREKey : 0x12345678>
            <IPv4Addr : 172.20.30.40>
            <ext : 0x>
      ### APN ###
       ### Hdr ###
        <Type : 71 (Access Point Name (APN))>
        <Len : 35>
        <spare : 0>
        <Inst : 0>
           <Data : my-apn.test-mno.mnc001.mcc001.gprs>
      ### SelectionMode ###
       ### Hdr ###
        <Type : 128 (Selection Mode)>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <spare : 0>
            <Val : 1 (MS provided APN, subscription not verified)>
            <ext : 0x>
      ### PDNType ###
       ### Hdr ###
        <Type : 99 (PDN Type)>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <spare : 0>
            <Val : 1 (IPv4)>
      ### PAA ###
       ### Hdr ###
        <Type : 79 (PDN Address Allocation (PAA))>
        <Len : 5>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <spare : 0>
            <PDNType : 1 (IPv4)>
            <PDNAddress : 0x00000000>
      ### MaximumAPNRestriction ###
       ### Hdr ###
        <Type : 127 (APN Restriction)>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <Val : 0>
            <ext : 0x>
      ### AggregateMaximumBitRate ###
       ### Hdr ###
        <Type : 72 (Aggregate Maximum Bit Rate (AMBR))>
        <Len : 8>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <APN-AMBR-UL : 1048576>
            <APN-AMBR-DL : 1048576>
      ### PCO ###
       ### Hdr ###
        <Type : 78 (Protocol Configuration Options (PCO))>
        <Len : 26>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <Ext : 1>
            <spare : 0>
            <Prot : 0 (PPP with IP PDP)>
            ### Config ###
             ### ProtConfigElt ###
              <ID : 32801 (IPCP)>
              <Len : 16>
              ### Cont ###
               <Code : 1 (Configure-Request)>
               <Id : 0>
               <Len : 16>
               ### Data ###
                ### NCPOpt ###
                 <Type : 129 (Primary DNS Server Address)>
                 <Len : 6>
                 <Data : 0x00000000>
                ### NCPOpt ###
                 <Type : 131 (Secondary DNS Server Address)>
                 <Len : 6>
                 <Data : 0x00000000>
             ### ProtConfigElt ###
              <ID : 13 (DNS server IPv4 address request)>
              <Len : 0>
              <Cont : 0x>
             ### ProtConfigElt ###
              <ID : 10 (IP address allocation via NAS signalling)>
              <Len : 0>
              <Cont : 0x>
      ### BearerContexttobecreated ###
       ### Hdr ###
        <Type : 93>
        <Len : 44>
        <spare : 0>
        <Inst : 0>
       ### Data ###
        ### EPSBearerID ###
         ### Hdr ###
          <Type : 73 (EPS Bearer ID (EBI))>
          <Len : 1>
          <spare : 0>
          <Inst : 0>
             ### Data ###
              <spare : 0>
              <Val : 5>
              <ext : 0x>
        ### S5S8USGWFTEID ###
         ### Hdr ###
          <Type : 87 (Fully Qualified Tunnel Endpoint Identifier (F-TEID))>
          <Len : 9>
          <spare : 0>
          <Inst : 2>
             ### Data ###
              <V4 : 1>
              <V6 : 0>
              <IF : 4 (S5/S8 SGW GTP-U interface)>
              <TEID_GREKey : 0x44554455>
              <IPv4Addr : 172.90.80.70>
              <ext : 0x>
        ### BearerLevelQoS ###
         ### Hdr ###
          <Type : 80 (Bearer Level Quality of Service (Bearer QoS))>
          <Len : 22>
          <spare : 0>
          <Inst : 0>
             ### Data ###
              <spare : 0>
              <PCI : 1>
              <PL : 15>
              <spare : 0>
              <PVI : 0>
              <QCI : 9>
              <MaxBitRateUL : 0>
              <MaxBitRateDL : 0>
              <GuaranteedBitRateUL : 0>
              <GuaranteedBitRateDL : 0>
              <ext : 0x>

In [6]: print(Msg[0].show()) # show only the header                                                                                                           
### GTPCHdr ###
 <Version : 2>
 <P : 0>
 <T : 1>
 <MP : 0>
 <spare : 0>
 <Type : 32 (Create Session Request)>
 <Len : 236>
 <TEID : 0x00123456>
 <SeqNum : 123456>
 <spare : 0>

In [7]: for ie in Msg[1]: 
    ...:     print(ie.show()) # iterating over each IE 
    ...:                                                                                                                                                  
    ### IMSI ###
     ### Hdr ###
      <Type : 1 (International Mobile Subscriber Identity (IMSI))>
      <Len : 8>
      <spare : 0>
      <Inst : 0>
         <Data : 001010987654321>
    ### MSISDN ###
     ### Hdr ###
      <Type : 76 (MSISDN)>
      <Len : 5>
      <spare : 0>
      <Inst : 0>
         <Data : 1122334455>
    [...]

In the GTP-C v2 specification, some IEs are defined as mandatory within a given message. This is however not always honored by some implementations. When trying to decode a message which is missing some mandatory IE(s), one will get an exception from pycrate e.g., GTPCDecErr: CreateSessionRespIEs: missing mandatory IE(s), BearerContextcreated (or whatever IE is found missing). There is a simple mean to manage the check for mandatory IEs, one only needs to configure the GTPCIEs class attribute as such:

In[29]: GTPCIEs.VERIF_MAND = False # to disable the check for mandatory IEs

In[30]: GTPCIEs.VERIF_MAND = True # to re-enable it

By doing so, one can eventually decode a GTPv2-C message that is not perfectly compliant to the 3GPP standard. Following is an example:

In [31]: buf = unhexlify('48210033098765430004c800020002005c0003000100074e001c0080c02305030000050080211004000010810600000000830600000000')                

In [32]: Msg, Err = parse_GTPC(buf)                                                                                                                       

In [33]: Err # we were unable to decode the buffer
Out[33]: 4

In [34]: Msg                                                                                                                                              

In [35]: GTPCIEs.VERIF_MAND = False # disabling the check for mandatory IEs

In [36]: Msg, Err = parse_GTPC(buf)                                                                                                                       

In [37]: Err # the decoding is OK this time
Out[37]: 0

In [38]: print(Msg.show())                                                                                                                                
### CreateSessionResp ###
 ### GTPCHdr ###
  <Version : 2>
  <P : 0>
  <T : 1>
  <MP : 0>
  <spare : 0>
  <Type : 33 (Create Session Response)>
  <Len : 51>
  <TEID : 0x09876543>
  <SeqNum : 1224>
  <spare : 0>
     ### CreateSessionRespIEs ###
      ### Cause ###
       ### Hdr ###
        <Type : 2 (Cause)>
        <Len : 2>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <Val : 92 (User authentication failed)>
            <spare : 0>
            <PCE : 0>
            <BCE : 0>
            <CS : 0>
            ### OffendingIE [transparent] ###
             <Type : 0>
             <Len : 0>
             <spare : 0>
             <Inst : 0>
      ### Recovery ###
       ### Hdr ###
        <Type : 3 (Recovery (Restart Counter))>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           <Data : 7>
      ### PCO ###
       ### Hdr ###
        <Type : 78 (Protocol Configuration Options (PCO))>
        <Len : 28>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <Ext : 1>
            <spare : 0>
            <Prot : 0 (PPP with IP PDP)>
            ### Config ###
             ### ProtConfigElt ###
              <ID : 49187 (PAP)>
              <Len : 5>
              ### Cont ###
               <Code : 3 (Authenticate-Nak)>
               <Id : 0>
               <Len : 5>
               ### Data ###
                <MsgLen : 0>
                <Msg : b''>
             ### ProtConfigElt ###
              <ID : 32801 (IPCP)>
              <Len : 16>
              ### Cont ###
               <Code : 4 (Configure-Reject)>
               <Id : 0>
               <Len : 16>
               ### Data ###
                ### NCPOpt ###
                 <Type : 129 (Primary DNS Server Address)>
                 <Len : 6>
                 <Data : 0x00000000>
                ### NCPOpt ###
                 <Type : 131 (Secondary DNS Server Address)>
                 <Len : 6>
                 <Data : 0x00000000>

In [39]: GTPCIEs.VERIF_MAND = True # we restore the check for mandatory IEs

In [40]: Msg = CreateSessionResp()                                                                                                                    

In [41]: Msg.from_bytes(buf) # Here we can see the exception raised by the decoding routine
---------------------------------------------------------------------------
GTPCDecErr                                Traceback (most recent call last)
<ipython-input-25-1552f17aa928> in <module>
----> 1 Msg.from_bytes(b)

~/python/pycrate_core/elt.py in from_bytes(self, char)
    650                          .format(self._name, type(char).__name__)))
    651         #
--> 652         self._from_char(char)
    653 
    654     def to_bytes(self):

~/python/pycrate_mobile/TS29274_GTPC.py in _from_char(self, char)
   4151         char._lb = char._cur + 8 * len_ies
   4152         # decode all IEs
-> 4153         self[1]._from_char(char)
   4154         # restore original char length
   4155         char._len_bit = char_lb

~/python/pycrate_mobile/TS29274_GTPC.py in _from_char(self, char)
   4056                 self._ie_mand.discard( (ie[0]['Type'].get_val(), ie[0]['Inst'].get_val()) )
   4057             if self._ie_mand:
-> 4058                 raise(GTPCDecErr('{0}: missing mandatory IE(s), {1}'\
   4059                       .format(self._name, ', '.join([self.MAND[k][1] for k in self._ie_mand]))))
   4060 

GTPCDecErr: CreateSessionRespIEs: missing mandatory IE(s), BearerContextcreated

It is also possible to only decode the header by instantiating the GTPCHdr class, or a list of information elements with the GTPCIEs class. Beware however, that depending of the message type, some specific internal nested IE structures are locally defined for specific messages' type, and will not be handled correctly by this generic GTPCIEs class.

Finally, in case we want to manipulate a given GTP-C v2 message, we can use one of the class from the GTPCDispatcher

Encoding GTP-C v2 messages

GTP-C v2 messages can be quite complex, and like with any complex protocol, we need to know exactly what to put in the message when crafting it. Some automation is carried by pycrate for length prefixes and few other parameters, but this is still required for the developper to know what values to put in most of the fields and information elements in the message.

When instantiating a GTP-C v2 class, without any arguments, by default we get the header set in the structure plus all the mandatory IEs with default (or dummy) values. We can also pass a structured value for both the header and IEs, so that the message class gets instantiated with the provided values and is ready to be encoded.

Here is an example with no value, where all mandatory IEs are setup with default values:

In [2]: from pycrate_mobile.TS29274_GTPC import *                                                                                                         

In [3]: Msg = CreateBearerReq()

In [4]: print(Msg.show())

### CreateBearerReq ###
 ### GTPCHdr ###
  <Version : 2>
  <P : 0>
  <T : 1>
  <MP : 0>
  <spare : 0>
  <Type : 95 (Create Bearer Request)>
  <Len : 53>
  <TEID : 0x00000000>
  <SeqNum : 0>
  <spare : 0>
     ### CreateBearerReqIEs ###
      ### LinkedEPSBearerID ###
       ### Hdr ###
        <Type : 73 (EPS Bearer ID (EBI))>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <spare : 0>
            <Val : 5>
            <ext : 0x>
      ### BearerContext ###
       ### Hdr ###
        <Type : 93>
        <Len : 36>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            ### EPSBearerID ###
             ### Hdr ###
              <Type : 73 (EPS Bearer ID (EBI))>
              <Len : 1>
              <spare : 0>
              <Inst : 0>
                 ### Data ###
                  <spare : 0>
                  <Val : 5>
                  <ext : 0x>
            ### BearerLevelQoS ###
             ### Hdr ###
              <Type : 80 (Bearer Level Quality of Service (Bearer QoS))>
              <Len : 22>
              <spare : 0>
              <Inst : 0>
                 ### Data ###
                  <spare : 0>
                  <PCI : 0>
                  <PL : 0>
                  <spare : 0>
                  <PVI : 0>
                  <QCI : 9>
                  <MaxBitRateUL : 10000>
                  <MaxBitRateDL : 10000>
                  <GuaranteedBitRateUL : 0>
                  <GuaranteedBitRateDL : 0>
                  <ext : 0x>
            ### TFT ###
             ### Hdr ###
              <Type : 84 (EPS Bearer Level Traffic Flow Template (Bearer TFT))>
              <Len : 1>
              <spare : 0>
              <Inst : 0>
                 ### Data ###
                  <Opcode : 0 (Ignore this IE)>
                  <E : 0 (no parameters list)>
                  <NumPktFilters : 0>
                  ### PktFilters ###

When values for IEs are provided, the behaviour is to add mandatory IEs with default values when those mandatory IEs are not set from the values provided. Here is an example where we set explicitely the ChargingCharacteristics and the BearerContext, and the IE LinkedEPSBearerID gets automatically added as it is a mandatory one:

In [16]: val = {'GTPCHdr': {'TEID': 0x12341234, 'SeqNum': 0x000102}, 'CreateBearerReqIEs': [{'Hdr': {'Type': 95}, 'Data': {'Val': 0x1122}}, {'Hdr': {'Type': 93}, 'Data': [{'Hdr': {'Type': 73}, 'Data': {'Val': 6}}]}]}

In [17]: Msg = CreateBearerReq(val=val)

In [18]: print(Msg.show())               
### CreateBearerReq ###
 ### GTPCHdr ###
  <Version : 2>
  <P : 0>
  <T : 1>
  <MP : 0>
  <spare : 0>
  <Type : 95 (Create Bearer Request)>
  <Len : 59>
  <TEID : 0x12341234>
  <SeqNum : 258>
  <spare : 0>
     ### CreateBearerReqIEs ###
      ### ChargingCharacteristics ###
       ### Hdr ###
        <Type : 95 (Charging Characteristics)>
        <Len : 2>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <Val : 0x1122>
            <ext : 0x>
      ### BearerContext ###
       ### Hdr ###
        <Type : 93>
        <Len : 36>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            ### EPSBearerID ###
             ### Hdr ###
              <Type : 73 (EPS Bearer ID (EBI))>
              <Len : 1>
              <spare : 0>
              <Inst : 0>
                 ### Data ###
                  <spare : 0>
                  <Val : 6>
                  <ext : 0x>
            ### BearerLevelQoS ###
             ### Hdr ###
              <Type : 80 (Bearer Level Quality of Service (Bearer QoS))>
              <Len : 22>
              <spare : 0>
              <Inst : 0>
                 ### Data ###
                  <spare : 0>
                  <PCI : 0>
                  <PL : 0>
                  <spare : 0>
                  <PVI : 0>
                  <QCI : 9>
                  <MaxBitRateUL : 10000>
                  <MaxBitRateDL : 10000>
                  <GuaranteedBitRateUL : 0>
                  <GuaranteedBitRateDL : 0>
                  <ext : 0x>
            ### TFT ###
             ### Hdr ###
              <Type : 84 (EPS Bearer Level Traffic Flow Template (Bearer TFT))>
              <Len : 1>
              <spare : 0>
              <Inst : 0>
                 ### Data ###
                  <Opcode : 0 (Ignore this IE)>
                  <E : 0 (no parameters list)>
                  <NumPktFilters : 0>
                  ### PktFilters ###
      ### LinkedEPSBearerID ###
       ### Hdr ###
        <Type : 73 (EPS Bearer ID (EBI))>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <spare : 0>
            <Val : 5>
            <ext : 0x>

Note that, for every IE, it is always possible to provide the Data value as raw buffer, or as structured value:

In [22]: val = {'GTPCHdr': {'TEID': 0x12341234, 'SeqNum': 0x000102}, 'CreateBearerReqIEs': [{'Hdr': {'Type': 95}, 'Data': {'Val': 0x1122}}, {'Hdr': {'Type': 93}, 'Data': b'ABCDEFGHIJKL'}]}

In [23]: Msg = CreateBearerReq(val=val)

In [24]: print(Msg.show())            
### CreateBearerReq ###
 ### GTPCHdr ###
  <Version : 2>
  <P : 0>
  <T : 1>
  <MP : 0>
  <spare : 0>
  <Type : 95 (Create Bearer Request)>
  <Len : 35>
  <TEID : 0x12341234>
  <SeqNum : 258>
  <spare : 0>
     ### CreateBearerReqIEs ###
      ### ChargingCharacteristics ###
       ### Hdr ###
        <Type : 95 (Charging Characteristics)>
        <Len : 2>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <Val : 0x1122>
            <ext : 0x>
      ### BearerContext ###
       ### Hdr ###
        <Type : 93>
        <Len : 12>
        <spare : 0>
        <Inst : 0>
           <Data : 0x4142434445464748494a4b4c>
      ### LinkedEPSBearerID ###
       ### Hdr ###
        <Type : 73 (EPS Bearer ID (EBI))>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <spare : 0>
            <Val : 5>
            <ext : 0x>

There is also a method init_ies(wopt=False, wpriv=False) bound to every GTPCIEs class that enables to reinitialize the sequence of IEs with only mandatory ones, or including all optional ones, the private one respectively. Beware that this method clears all IEs values that were set in the message previously. This way, we obtain something like the largest (or richest) possible GTP-C message, while still staying compliant to the specification, by including all optional IEs:

In [5]: Msg[1].init_ies(wopt=True)

In [6]: print(Msg.show())
### CreateBearerReq ###
 ### GTPCHdr ###
  <Version : 2>
  <P : 0>
  <T : 1>
  <MP : 0>
  <spare : 0>
  <Type : 95 (Create Bearer Request)>
  <Len : 328>
  <TEID : 0x00000000>
  <SeqNum : 0>
  <spare : 0>
     ### CreateBearerReqIEs ###
      ### LinkedEPSBearerID ###
       ### Hdr ###
        <Type : 73 (EPS Bearer ID (EBI))>
        <Len : 1>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            <spare : 0>
            <Val : 5>
            <ext : 0x>
      [...]
      ### PGWChangeInfo ###
       ### Hdr ###
        <Type : 214>
        <Len : 12>
        <spare : 0>
        <Inst : 0>
           ### Data ###
            ### AlternativePGWCSMFIPAddress ###
             ### Hdr ###
              <Type : 74 (IP Address)>
              <Len : 0>
              <spare : 0>
              <Inst : 0>
                 <Data : b''>
            ### NewPGWCSMFIPAddress ###
             ### Hdr ###
              <Type : 74 (IP Address)>
              <Len : 0>
              <spare : 0>
              <Inst : 1>
                 <Data : b''>
            ### PGWSetFQDN ###
             ### Hdr ###
              <Type : 215 (PGW Set FQDN)>
              <Len : 0>
              <spare : 0>
              <Inst : 0>
                 ### Data ###
                  <FQDN : >
                  <ext : 0x>

With only the mandatory IEs in a CreateBearerRequest message, there are only 2 IEs. In GTP-C v2 and PFCP, IEs are structured in a Sequence(), which requires to access each IE by its index. While in GTP-C v1, IEs are structured in an Envelope(), which enables to access each IE by its name additionally.

In [9]: Msg[1][0][1]
Out[9]: <Data : <spare : 0><Val : 5><ext : 0x>>

In [10]: Msg[1][1][1]
Out[10]: <Data : <EPSBearerID : <Hdr : <Type : 73 (EPS Bearer ID (EBI))><Len : 1><spare : 0><Inst : 0><TypeExt [transparent] : 0>><Data : <spare : 0><Val : 5><ext : 0x>>><BearerLevelQoS : <Hdr : <Type : 80 (Bearer Level Quality of Service (Bearer QoS))><Len : 22><spare : 0><Inst : 0><TypeExt [transparent] : 0>><Data : <spare : 0><PCI : 0><PL : 0><spare : 0><PVI : 0><QCI : 9><MaxBitRateUL : 10000><MaxBitRateDL : 10000><GuaranteedBitRateUL : 0><GuaranteedBitRateDL : 0><ext : 0x>>><TFT : <Hdr : <Type : 84 (EPS Bearer Level Traffic Flow Template (Bearer TFT))><Len : 1><spare : 0><Inst : 0><TypeExt [transparent] : 0>><Data : <Opcode : 0 (Ignore this IE)><E : 0 (no parameters list)><NumPktFilters : 0><PktFilterIds [transparent] : ><PktFilters : ><Parameters [transparent] : >>>>

As we have here GTPCIEs into GTPCIEs for the BearerContext IE (also called Grouped IEs in the specification, or nested IEs), we can do the same enumeration for the content of the IE:

In [15]: Msg[1][1][1][0][1]                                                                                                                                 
Out[15]: <EBI : <spare : 0><Val : 5><ext : 0x>>

In [16]: Msg[1][1][1][1][1]                                                                                                                                 
Out[16]: <Data : <spare : 0><PCI : 0><PL : 0><spare : 0><PVI : 0><QCI : 9><MaxBitRateUL : 10000><MaxBitRateDL : 10000><GuaranteedBitRateUL : 0><GuaranteedBitRateDL : 0><ext : 0x>>

In [17]: Msg[1][1][1][2][1]                                                                                                                                 
Out[17]: <Data : <Opcode : 0 (Ignore this IE)><E : 0 (no parameters list)><NumPktFilters : 0><PktFilterIds [transparent] : ><PktFilters : ><Parameters [transparent] : >>

And we can set each IE content by providing values according to each IE structure, e.g.:

In [23]: Msg[1][1][1][1][1].set_val({'PCI': 1, 'PL': 1, 'PVI': 1, 'QCI': 7, 'MaxBitRateUL': 100000, 'MaxBitRateDL': 100000})                                

In [24]: Msg[1][1][1][1][1]                                                                                                                                 
Out[24]: <Data : <spare : 0><PCI : 1><PL : 1><spare : 0><PVI : 1><QCI : 7><MaxBitRateUL : 100000><MaxBitRateDL : 100000><GuaranteedBitRateUL : 0><GuaranteedBitRateDL : 0><ext : 0x>>

If we want to set the infamous BearerTFT sub-IE, we need however to go through the almighty structure from the TS 24.008 specification.

We can also add or remove individual IE one by one within a message, by using the add_ie() and rem_ie() methods, providing the IE type and optionally instance and value:

In [29]: print(Msg[1][-1][1].show())                                                                                                                      
        ### CreateBearerRequest_PGWChangeInfo ###
         ### AlternativePGWCSMFIPAddress ###
          ### GTPCIEHdr ###
           <Type : 74 (IP Address)>
           <Len : 0>
           <spare : 0>
           <Inst : 0>
              <IPAddress : 0x>
         ### NewPGWCSMFIPAddress ###
          ### GTPCIEHdr ###
           <Type : 74 (IP Address)>
           <Len : 0>
           <spare : 0>
           <Inst : 1>
              <IPAddress : 0x>
         ### PGWSetFQDN ###
          ### GTPCIEHdr ###
           <Type : 215 (PGW Set FQDN)>
           <Len : 0>
           <spare : 0>
           <Inst : 0>
              ### PGWSetFQDN ###
               ### FQDN ###
               <ext : 0x>

In [30]: Msg[1].rem_ie(214) # removing the PGWChangeInfo IE

In [31]: print(Msg[1][-1][1].show())                                                                                                                      
        ### CreateBearerRequest_LoadControlInformation ###
         ### LoadMetric ###
          ### GTPCIEHdr ###
           <Type : 182 (Metric)>
           <Len : 1>
           <spare : 0>
           <Inst : 0>
              <Metric : 0>
         ### LoadControlSequenceNumber ###
          ### GTPCIEHdr ###
           <Type : 183 (Sequence Number)>
           <Len : 4>
           <spare : 0>
           <Inst : 0>
              <SequenceNumber : 0>
         ### ListofAPNAndRelativeCapacity ###
          ### GTPCIEHdr ###
           <Type : 184 (APN and Relative Capacity)>
           <Len : 2>
           <spare : 0>
           <Inst : 0>
              ### APNAndRelativeCapacity ###
               <RelativeCapacity : 0>
               <APNLen : 0>
               ### APN ###
               <ext : 0x>

In [32]: Msg[1][1][1].rem_ie(94) # removing the ChargingID IE nested in the BearerContext IE

Dealing with PFCP and GTP-C version 0 and 1

The PFCP protocol structure is very close to the GTP-C v2 one. Therefore, the way to handle message encoding and decoding is almost the same as with GTP-C v2. The few differences are with the header and IE field names, which are different ; it's not the same protocol, after all.

The GTP-C version 0 and 1 protocols are also close to version 2, but with the main difference that IEs are all stored in an Envelope, with a fixed order. Optional IEs are by default set as transparent. IEs can therefore be selected by their name, instead of their index as in version 2. For this reason too, the value passed to any GTPIEs subclass in GTP-C v0 or v1 can be a dict with {IE name: IE value}, instead of a list of IE value.

For the decoding step, there are similar parsing functions provided in each modules and dictionnaries providing every message classes, respectively:

  • parse_PFCP(buf) and PFCPDispatcher for the PFCP protocol
  • parse_GTPv0(buf) and GTPv0Dispatcher for the GTP-C v0 protocol
  • parse_GTP(buf) for the GTP-C v1 protocol

Another subtlelity with GTP-C v1 is that UpdatePDPCtxtReq and UpdatePDPCtxtResp message structures differ in case they are received by an SGSN or by a GGSN. For this reason, two parsing functions, and two message dictionnaries are provided:

  • parse_GTP_SGSN(buf) and GTPDispatcherSGSN for messages received by an SGSN
  • parse_GTP_GGSN(buf) and GTPDispatcherGGSN for messages received by a GGSN

Dealing with GTP-U

The GTP-U protocol is slightly different from GTP-C. In the earliest GSM time, both GTP-U and GTP-C variants were actually defined as a single protocol. However, the User-Plane variant has evolved into a different way: In GTP-U, the only message types available are basically for echo-ing endpoints, and transporting user-plane IP packets (also often called GPDU).

With the extended use of GTP-U not only in cellular core networks, but also in radio access networks (starting in 3G, extended in 4G, and again extended in 5G), GTP-U messages are structured in a sensible different way. In a GPDU, we have the GTP Header, which is often extended with Header Extensions, embedding signaling information together with the user-plane payload. Extensions can be chained. The User-Plane payload is most of the time IP packets, but could also be 5G RLC radio frames in 5G radio access networks.

For the decoding step, a similar function and dictionnary is provided in the module: parse_GTPU(buf) and GTPUDispatcher.

Going further

This serie of GTP and PFCP protocols remains quite complex, with a long history of changes and extensions. Any developer interested in working with pycrate should really try to go check the source code of each module, in order to understand all the tiny greedy details of those protocols. There are moreover additional data structures provided, that may help to validate callflows, depending of endpoint engaged in a communications.

As a final note, pycrate provides just ways to encode and decode messages. Then, it is up to each developper to use this into an application, such as an SGW or MME emulator, with all the appropriate subscriber's context management (which is a pain to say less) !