The Corelatus Blog
E1/T1 and SDH/SONET telecommunications

Decoding MTP-3 and ISUP

Posted March 25th 2009

Sometimes, you want to look at the signalling on an E1 and use it to figure out when telephone calls start and stop. In SS7 networks, call setup and tear-down is done by the ISUP layer, which fits in to the SS7 stack like this:

Layer 4: ISUP
Layer 3: MTP-3
Layer 2: MTP-2
Layer 1: MTP-1 (typically an 2Mbit/s E1 or a 1.5Mbit/s/T1)

If you have a GTH connected to the E1 you're interested in, either via a DXC or a monitor point, the GTH takes care of layers one and two. That leaves MTP-3 and ISUP to you.

The easiest way to decode MTP-3 and ISUP is to let wireshark do it for you. There's a note about how to do that on Corelatus' official site. But this blog entry is about how to decode MTP-3 and ISUP yourself.

A signal unit (packet)

In SS7, packets are usually called "signal units". Here's what an SS7 signal unit looks like 'on the wire', octet by octet, with MTP-2 and MTP-1 already decoded:

  8d c8 1f 85 02 40 00 00  35 00 01 00 21 00 0a 02
  02 08 06 01 10 12 52 55  21 0a 06 07 01 11 13 53
  55 00 6e 00


The start of the packet is the MTP-2 (ITU-T Q.703) and MTP-3 (ITU-T Q.704) headers. These headers are easy to decode because they are always fixed-length:

Octet(s) Value Purpose
00--01 8d c8 MTP-2 sequence numbers, safe to ignore
02 1f MTP-2 length indicator. Anything less than 3 is reserved for MTP-2 itself and should be discarded.
03 85 MTP-2 SIO. The SIO tells us which 'service' the signal unit is intended for. Q.704 sections14.2.1 and 14.2.2 tell us that anything ending in hex 5 is for ISUP.
04--07 02 40 00 00 MTP-3 Routing label. The routing label is just a "from" and "to" address in the SS7 network. For most applications we can ignore it. Q.704 figure 3 shows what's in the routing label.

Upshot: to see calls start and stop, all we have to do for MTP-3 is:

  1. Look at the length indicator (offset 2) and discard any signal unit where it's less than 3.
  2. Look at the SIO (offset 3). Discard if (SIO & 0x0f != 5)


The rest of the signal unit is ISUP. Annex C in ITU-T Q.767 tells us how to decode ISUP. ISUP is fiddly because there are several types of ISUP packets, because several of those types have optional fields and because some of those fields are variable length. Here are the octets we have left after removing MTP-2 and MTP-3:

  35 00 01 00 21 00 0a 02
  02 08 06 01 10 12 52 55
  21 0a 06 07 01 11 13 53
  55 00 6e 00

The first two octets are the CIC. The third octet is the Message type.

The CIC (Q.767 C.1.2) tells us which circuit this call uses. All the signalling for one call has the same CIC. In ITU networks, it's a 12-bit value packed into the field in little-endian byte order. In this case CIC=0x0035. We're sniffing an E1 line, so C.1.2.a tells us that the lower five bits correspond to the timeslot (timeslot 5) and the rest identifies the E1 itself.

The Message Type (Q.767 Table C-3) field tells us what sort of ISUP message this signal unit is. 0x01 is an IAM. 0x10 is RLC. For a minimal "show me what calls are going through the system" hack, we only need to look at the IAM (comes at the start of the call, contains the A and B numbers) and the RLC (sent when the call is finished) messages.

Now we know that the CIC=0x35, that the message is an IAM and we still have about a dozen octets to decode. Q.767 table C-16 tells us how to decode an IAM. There are some uninteresting fixed-length fields followed by the B number and then the A number. Look at the code (or Q.767, section C.3.7) if you're interested in the details. All we really care about is that these octets

  06 01 10 12 52 55 21

represent the B number: 21255512. You can see the number in the raw data if you skip the first three octets and swap every second digit.

Turning those ISUP steps into an algorithm to decode one signal unit:

  1. Save the CIC
  2. Is the message an IAM? Decode it as an IAM, which is fiddly.
  3. Is the message an RLC? Just print the CIC.

That's all you need to do to make a simple system which prints the start and end of each call. To do something useful, you need to maintain a table of in-progress calls and match up the IAM and RLC messages with the same CIC. You also need to handle things like systems restarting.

Further reading

The ITU now have most of their standards freely available at So one way to learn more about MTP-3 and ISUP is to read the standards, e.g. all the Q-series standards about signalling are here.

Erlang code

Everything discussed above is implemented in the ss7_sniffer.erl example. It makes good use of Erlang's binary syntax, e.g. here's the MTP-3 decoder:

    mtp3(<<_Sub:4, Service_indicator:4>>, <<DPC:14, OPC:14, SLS:4,
						    Rest/binary>>) ->
	case Service_indicator of
	0 -> % Management
	1 -> % Test/maintenance
	3 -> % SCCP
	5 ->
        isup(DPC, OPC, SLS, Rest);
	9 -> % B-ISUP; similar to ISUP, but not compatible.
	X ->
        io:fwrite("ignoring SU with unexpected service indicator=~p\n", [X])

It looks a lot like one of the examples in the original paper about the binary syntax.

Python Code

The same thing done in Python is fairly straightforward once you discover the Python 'struct' library, which is basically the same thing as PERL's pack/unpack. The code is in, inside the GTH python examples zip.


Bartosz, 28. January 2010

Hey. Just stumbled on this page while I was googling after Mtp3 procedures. I'm wondering if you have any materials that you could share regarding link setup - I'm working on open source stacks for ss7. Im able to setup mtp2,3 and layer 4(signle link), now I'm working on getting it work with more than one and frankly Qs for mtp3 are not very helpful.


Matt, 28. January 2010

@Bartosz: I only really work with MTP2. My customers use MTP3, so I know quite a bit about it, but you probably know more. For MTP2, the standard is difficult to read, but comprehensive. MTP3 seems harder. I have a copy of "Signalling in Telecommunications Networks" by van Bosse et al, it's useful for getting started, but I always end up trudging through the standards.

Interesting to see someone working on an open SS7 stack. Up until now, I've only been aware of (written in C, some parts seem complete, the project seems to be a one-man effort).

Or: I don't think I can help much, but what you're doing looks interesting.


Emza, September 15, 2009

I found it very important but still not enough for me. I was looking for ISUP MTP-3 and ISUP decoding in C or in C#(not only start and end singnal unit but general which includes all message types). I would like to thank you but can help in my case please.

Thanks again

Matt, September 15, 2009

@Emza, decoding the rest of the message types is 'just' a matter of working your way through ITU-T Q.767 (ITU standards are now available for free at and writing code to handle each and every section in the standard. Same basic idea as the messages I did. Doing it all is a few days of drudge work, which is why I haven't done it here.

About doing it in C or C#. I know nothing about C#. I work with C most days, though. Doing this sort of protocol decoding in C is fairly straightforward, but it's inevitably going to be more tedious than doing it in something like python, perl or erlang. In some applications the performance gain might be worth the extra effort.

Meskerem David, September 15, 2009

It is a great job. Keep working...

Can u please me one brief algorithm of decoding ISUP. Especially I am not clear how to decode the variable length parameters and the optional ones.


Matt, September 15, 2009

@Meskerem, In an IAM, the A-number and B-number parameters are variable length. The algorithm for finding the B-number start is trivial, it's just a pointer offset. Here's what it looks like in the python example code:


      def isup_iam(_, CIC, sif):
      # First 5 octets can be ignored
      bnum_pointer = ord(sif[5])
      bnum = sif[5 + bnum_pointer:]
      print "IAM called party: %s CIC=%d" \
      % (isup_number(bnum), CIC)

      # And here's how the example code figures out the length:

      # Decode an ISUP number, as per C 3.7
      def isup_number(num):
      length = ord(num[0]) - 2

The A-number is trickier because it's an optional parameter. The python sample code steps through the optional parameters looking for the A-number.

(the site also has exactly the same decoding routines in Perl and Erlang)

(Edit 2017-02-20: Thanks to Moy for pointing out that the code to find the A-number was a nasty hack which assumed that the A-number was the first optional parameter present.)

Permalink | Tags: erlang, GTH, telecom-signalling, python

Erlang example code for GTH

Posted February 1st 2009

Years ago, I wrote a few bits of example code to help customers get started with controlling a GTH. If you were kind, you'd call them minimal, e.g. the Erlang one was about 20 lines long.

None of our customers that use Erlang were very impressed by that, so I'd hand out part of the code we use to do API testing (a cut-down XML parser and a little library to handle sockets) along with sternly issued instructions that this was just example code and that live systems needed something more robust. Not great, but I had other things to do.

I finally got around to completing and releasing a proper native Erlang API for the GTH. No (visible) XML. It's a gen_server, so it's well-behaved and simple enough so that you can do almost anything from the Erlang shell, e.g. to turn on an E1:

1> {ok, A} = gth:start_link("").
{ok, <0.44.0>}
2> gth:set(A, "pcm1B", [{"status", "enabled"}]).

and put an audio file on a timeslot:

3> {ok, Bin} = file:read_file("/tmp/resample_q_alaw.raw").
4> {ok, _ID, P_socket} = gth:new_tcp_player(A, "1B", 2).
5> gen_tcp:send(P_socket, Bin).
6> flush().

There's a direct mapping between GTH commands and function names in the gth.erl module, e.g. the XML <set> command becomes gth:set(). While writing the gth.erl module, I also rewrote our entire API test suite (60 modules, 30kloc) to use the gth.erl interface. It ended up under 20kloc. I'd guess half of that reduction comes from gth.erl being nicer to use and the other half comes from cleaning up "while I was at it".

The code, with examples:

While writing gth.erl, I had a go at doing something similar in Python. But that's another story.

Permalink | Tags: erlang, GTH