Tetra sniffer HOWTO

Several people asked me how to build a Tetra sniffer and because it was complicated and tricky, I decided to consolidate the software into several git repositories and write this howto.

We will build a sniffer for an unencrypted network. It will save voice traffic and SDS messages.

This is how it will look like:

We suspect there is a bug in airspy_rx with sample format 0. After several minutes, it sends us a NaN. The workaround (if you want to use Airspy) is to use osmosdr-input from Kukuruku, an interactive SDR client.



  • A system where sizeof(int) == sizeof(int*). We have to run C from 1995 :-|. amd64 with -m32 and armel are tested to work.
  • A SDR receiver with samplerate that is an integer multiple of 25000 Hz.
  • A fast CPU, the faster the better :-) (but if you limit yourself to ~10 channels you can run this even on a Raspberry Pi)

SW from your distribution repository:

  • gnuradio
  • lib32gcc-6-dev libc6-dev:i386 (or similar depending on GCC version and architecture)
  • libtalloc-dev libpcsclite-dev realpath
  • tshark
  • sqlite3
  • python3
  • sox

SW that is probably not in your repository:


Install jcf from blobutils to your $PATH and run build.sh to get gr-pack and gr-unpack; get them to your $PATH too.

Install libosmocore-dev package (Ubuntu 16.10+, Debian 9+) or build libosmocore by hand (autoreconf -i && ./configure –disable-pcsc && make && make install).

Build tetra-listener (instructions are in README).

Build fcl.

Try running sds-parser.py from tetra-multiframe-sds/. If it logs “tshark died!”, you have new tshark that has renamed some fields. Change it in config.py.


tetra-listener/sniff-utils/tetra-run.sh and radio-tetra/tetra.sh (path to tetra?.py) contain paths to other executables that need to be reachable.

We use an ugly hack with two demodulators and two rtl-sdrs because our network is so wide one rtl-sdr is not enough. The two demodulators are tetra1.py and tetra2.py and they differ only in some paths (diff it). If you want to use only one radio, you can just use tetra.py from examples/ in fcl distribution.



ip6tables -A INPUT -p UDP --dport 4729 -j DROP
iptables -A INPUT -p UDP --dport 4729 -j DROP

Calibrate your SDR and edit PPMs in tetra-run.sh.

Use your favorite SDR tool to look for Tetra BTSs and determine the center frequencies you want to listen on.

If you have different samplerate than 1.8M, edit -n and -s parameters of FCL and of course the samplerate parameter of fir.py.

If samplerate of your SDR is not an integer multiple of 36000 (2*channel symbol rate), use the nearest lower step. If the difference is too big (where “too big” probably means something like 100 Hz), it won't work. Tweaking the Omega parameter of MPSK demodulator helps. E.g. when using airspy with 2.5MHz rate, set step to 69 and omega to (2500000)/69/18000 = 2.0129.

If you have relatively slow CPU, you may want to run FCL in multiple threads. Edit the -t parameter.

Run it and use gethisto FCL command (echo gethisto | nc localhost 3333) to get sane gain settings.

You can use this oneliner to get the list of 20 strongest channels:

echo getpwr 3 | nc localhost 3333 | while read line; do n=`echo $line | cut -d " " -f 1`; if [ $(( ( $n - 1 ) % 3 )) -eq 0 ]; then echo $(( ($n - 1) / 3)) `echo $line | cut -d " " -f 2`; fi;done | LANG=C sort -nk 2 | tail -n 20 | cut -d " " -f 1 | tr "\n" ,

Edit enabled channels in tetra-run.sh so they match your channels with the strongest power. Do not enable more than 30 channels per SDR (or change the offset in tetra2.py:128).

If you change the number of channels, you need to change this number in the demodulator too (self.channels).

You can change the loglevel of sds-parser.py in tetra-run.sh to DBG.

If you now run tetra-run.sh, it should work. You should see some GSMTAP traffic on localhost:udp/4729. SDSs should be seen and recordings should pile up in tetra-rec. The number in the filename after the timestamp is the timeslot, “o” is the channel and “i” is the SSI (group ID). The SSI is not always correctly recognized because the sniffer does not handle the FDM multiplex correctly.

Use cdp to play these files. Or play the OGG files with any audio player.


It can be useful to check after a day or so which channels produce audio data (ls|grep bz2|cut -d “o” -f 2|cut -d “.” -f 1|sort -n | uniq -c) and change the ones that do not for some new channels.

You may want to add “.timeout 5000” to your ~/.sqliterc.

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Noncommercial-Share Alike 4.0 International
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki