4 Commits
Author SHA1 Message Date
catarrh 6ed3a7b7d6 README: link the project website next to the Telegram channel
Project news now lives on https://atroshin.ru as well; both links sit on the
same line near the top, in the English and the Russian README.
2026-10-26 22:12:05 +03:00
catarrh 903ead9791 GSMTAP: --gsmtap-bind to listen beyond loopback (containers)
A container's 127.0.0.1 is its own loopback, so a GSMTAP feed sent to the
container's bridge address never reached the listener: it was hard-bound to
127.0.0.1.

- `simtrace-analyser-server --gsmtap-bind ADDR` (default 127.0.0.1) is passed
  through GsmtapListener to GsmtapReceiver (whose bind_ip already existed),
  including the receiver re-created by a start() after close.  The startup
  banner prints the effective bind address next to the port.
- READMEs (EN/RU) document the option, naming the container case.
- tests: the listener forwards the address on construction and on start(),
  binds 0.0.0.0, and keeps the loopback default.

Verified end-to-end: with --gsmtap-bind 0.0.0.0 the socket listens on
0.0.0.0:14732 and an ATR + APDU sent to 127.0.0.1:14732 landed in the capture
session (2 messages); without the flag it listens on 127.0.0.1.
557 Python + 80 Node tests.
2026-10-26 14:45:58 +03:00
catarrh c810ff50cf README: link the project's Telegram channel
Adds the channel (project news and release announcements) right after the
introduction, in both the English and the Russian README.
2026-10-23 21:44:13 +03:00
catarrh 5fcd8e9fd6 Relicense to MIT (v2.7.13)
Release / release (push) Canceled after 0s
The project's only runtime dependency is PyUSB (BSD-3-Clause) and it bundles
no third-party code (the PWA's DES/3DES is written from the FIPS 46-3 tables,
AES uses WebCrypto, the decoders come from the ISO 7816/ETSI/3GPP specs), so
nothing forced GPL; the GPL-2.0-or-later declaration is replaced with MIT.

- LICENSE: the MIT text, Copyright (c) 2026 Anton Troshin.
- pyproject: license = "MIT" + license-files = ["LICENSE"] (PEP 639), and the
  build requirement moves to setuptools>=77 for the SPDX form.  The wheel now
  carries dist-info/licenses/LICENSE and License-Expression: MIT, the sdist
  and the runtime zip carry LICENSE at the top.
- READMEs (EN/RU): a License section (original decoders/DES implementation,
  PyUSB as the only runtime dependency).
- tests: the packaging test pins the MIT declaration and the LICENSE text;
  the release-zip test requires LICENSE in the archive.
- version 2.7.13; SW cache v95.

Copies published before this tag (v2.7.10-v2.7.12) remain under the license
they were released with.
2026-10-21 01:03:55 +03:00
15 changed files with 117 additions and 18 deletions

No files matched your search

+2
View File
@@ -18,3 +18,5 @@ is the runtime tree only (packages, PWA, launchers, docs — no tests, no git):
extract it anywhere and run `start.bat` (Windows) or `./start.sh`
(Linux/macOS). Python 3 has to be on `PATH`; `pip install pyusb` is only
needed for direct SIMtrace2 capture.
The project is MIT-licensed (see `LICENSE`).
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Anton Troshin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -1,3 +1,4 @@
graft frontend
include LICENSE
include README.md README_RUS.md 70-simtrace2-pysniff.rules
include start.sh start.bat sniff-start.sh sniff-start.bat
+14 -3
View File
@@ -9,6 +9,8 @@ also bundles **simtrace2-pysniff**, a Python replacement for
`simtrace2-sniff` that captures from Osmocom SIMtrace2 hardware and can feed
GSMTAP into this analyser.
📢 Project news and updates: [atroshin.ru](https://atroshin.ru) | [Telegram channel](https://t.me/youeyeseesee)
![SIMtrace Analyser PWA](frontend.png)
## Components
@@ -105,7 +107,7 @@ Everything is on the
**Wheel** (the PWA is inside the package):
```sh
pip install ./simtrace_analyser-2.7.12-py3-none-any.whl
pip install ./simtrace_analyser-2.7.13-py3-none-any.whl
# then run (the UI is served at http://127.0.0.1:8081/):
simtrace-analyser-server --capture gsmtap
@@ -119,7 +121,7 @@ Fedora), install into a virtual environment first:
shown there):
```sh
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.12/simtrace_analyser-2.7.12-py3-none-any.whl
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.13/simtrace_analyser-2.7.13-py3-none-any.whl
```
With the GitHub CLI, download the latest wheel without knowing the version:
@@ -133,7 +135,7 @@ pip install ./simtrace_analyser-*.whl
without the git history: unzip, `cd` into it and use the launchers below
(`./start.sh` on Linux/macOS, `start.bat` on Windows).
**Zip (Windows-friendly, no installer)** — `simtrace-analyser-2.7.12.zip` is
**Zip (Windows-friendly, no installer)** — `simtrace-analyser-2.7.13.zip` is
the runtime tree only (packages, PWA, launchers, docs; no tests, no git):
download it, extract it anywhere and run `start.bat` (Windows) or
`./start.sh` (Linux/macOS). Python 3 has to be on `PATH`; `pip install pyusb`
@@ -340,6 +342,8 @@ Server options:
--db FILE SQLite database path (default: ~/.simtrace-analyser/sessions.db)
--capture MODE gsmtap | direct | disabled (default: gsmtap)
--gsmtap-port PORT UDP port for the GSMTAP listener (default: 4729)
--gsmtap-bind ADDR UDP bind address for the GSMTAP listener (default: 127.0.0.1;
use 0.0.0.0 to receive from a Docker bridge or another host)
--web-dir PATH PWA static files directory (default: the PWA bundled in
the installed package, or <repo>/frontend from source)
```
@@ -510,3 +514,10 @@ Use `--inactivity-timeout` to also trigger reconnect on silent firmware hangs.
- **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** —
FX2LP-based passive ISO 7816 sniffer that emits the same GSMTAP-SIM stream
on UDP 4729 (accepted by this project's GSMTAP listener).
## License
MIT — see [LICENSE](LICENSE). The decoders and the PWA's DES/3DES
implementation are original, written from the ISO 7816, ETSI and 3GPP
specifications; no third-party code is bundled. PyUSB (BSD-3-Clause) is a
runtime dependency for direct hardware capture.
+14 -3
View File
@@ -9,6 +9,8 @@ SIM/UICC из UDP-потока GSMTAP (или импортирует файлы
на Python, которая захватывает трафик с оборудования Osmocom SIMtrace2
и может передавать GSMTAP этому анализатору.
📢 Новости и обновления проекта: [atroshin.ru](https://atroshin.ru) | [Telegram-канал](https://t.me/youeyeseesee)
![PWA SIMtrace Analyser](frontend.png)
## Компоненты
@@ -111,7 +113,7 @@ git) или склонировать репозиторий для разраб
**Wheel** (PWA внутри пакета):
```sh
pip install ./simtrace_analyser-2.7.12-py3-none-any.whl
pip install ./simtrace_analyser-2.7.13-py3-none-any.whl
# затем запуск (интерфейс отдаётся на http://127.0.0.1:8081/):
simtrace-analyser-server --capture gsmtap
@@ -125,7 +127,7 @@ Fedora), сначала создайте виртуальное окружени
указанную там):
```sh
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.12/simtrace_analyser-2.7.12-py3-none-any.whl
pip install https://github.com/anttro/simtrace-analyser/releases/download/v2.7.13/simtrace_analyser-2.7.13-py3-none-any.whl
```
С GitHub CLI можно скачать wheel последнего релиза, не зная версии:
@@ -139,7 +141,7 @@ pip install ./simtrace_analyser-*.whl
дерево без истории git: распакуйте, перейдите в каталог и используйте скрипты
запуска ниже (`./start.sh` в Linux/macOS, `start.bat` в Windows).
**Zip (для Windows, без установки)** — `simtrace-analyser-2.7.12.zip`
**Zip (для Windows, без установки)** — `simtrace-analyser-2.7.13.zip`
содержит только рабочее дерево (пакеты, PWA, скрипты запуска, документацию;
без тестов и git): скачайте, распакуйте в любое место и запустите `start.bat`
(Windows) или `./start.sh` (Linux/macOS). Python 3 должен быть в `PATH`;
@@ -349,6 +351,8 @@ TS 51.011), поэтому FID, повторно используемый в р
--db FILE путь к базе SQLite (по умолчанию: ~/.simtrace-analyser/sessions.db)
--capture MODE gsmtap | direct | disabled (по умолчанию: gsmtap)
--gsmtap-port PORT UDP-порт листенера GSMTAP (по умолчанию: 4729)
--gsmtap-bind ADDR UDP-адрес привязки листенера GSMTAP (по умолчанию: 127.0.0.1;
используйте 0.0.0.0, чтобы принимать из Docker-сети или с другого хоста)
--web-dir PATH каталог статических файлов PWA (по умолчанию: PWA внутри
установленного пакета или <repo>/frontend при запуске из исходников)
```
@@ -530,3 +534,10 @@ for msg in session.iter_messages():
- **[sigrok_iso7816_stream](https://github.com/anttro/sigrok_iso7816_stream)** —
пассивный ISO 7816 сниффер на базе FX2LP, выдающий такой же поток
GSMTAP-SIM на UDP 4729 (принимается GSMTAP-листенером этого проекта).
## Лицензия
MIT — см. [LICENSE](LICENSE). Декодеры и реализация DES/3DES в PWA написаны
самостоятельно по спецификациям ISO 7816, ETSI и 3GPP; сторонний код в проект
не включён. PyUSB (BSD-3-Clause) — зависимость для прямого захвата с
оборудования.
+1 -1
View File
@@ -3389,7 +3389,7 @@ function onSearchChange() {
}
// ──────────────────── Versioning ────────────────────
const PWA_VERSION = '2.7.12';
const PWA_VERSION = '2.7.13';
const MIN_SERVER_VERSION = '2.3.0';
function parseVersion(v) {
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'simtrace-analyser-v94';
const CACHE = 'simtrace-analyser-v95';
const PRECACHE = [
'index.html',
+4 -3
View File
@@ -1,13 +1,14 @@
[build-system]
requires = ["setuptools>=64"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
name = "simtrace-analyser"
version = "2.7.12"
version = "2.7.13"
description = "SIMtrace Analyser — APDU trace analyser for GSMTAP/PCAP captures (bundles the simtrace2-pysniff utility)"
readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GPL-2.0-or-later"}
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.9"
dependencies = [
"pyusb>=1.2",
+7 -2
View File
@@ -1,7 +1,8 @@
"""simtrace-analyser-server — CLI entry point.
Usage:
simtrace-analyser-server --capture gsmtap [--gsmtap-port 4729] [--port 8081]
simtrace-analyser-server --capture gsmtap [--gsmtap-bind 0.0.0.0]
[--gsmtap-port 4729] [--port 8081]
simtrace-analyser-server --capture direct [--port 8081]
"""
@@ -46,6 +47,9 @@ def main():
help='Capture mode: gsmtap (listen UDP 4729), direct (SIMtrace2 USB), or disabled (no capture)')
p.add_argument('--gsmtap-port', type=int, default=4729,
help='UDP port for GSMTAP listener (default: 4729)')
p.add_argument('--gsmtap-bind', default='127.0.0.1', metavar='ADDR',
help='UDP bind address for the GSMTAP listener '
'(default: 127.0.0.1; use 0.0.0.0 in a container)')
p.add_argument('--log-interval', type=float, default=60.0,
help='Seconds between capture liveness heartbeat logs '
'(default: 60.0)')
@@ -61,7 +65,7 @@ def main():
capture = None
if args.capture == 'gsmtap':
backend = GsmtapListener(bind_port=args.gsmtap_port)
backend = GsmtapListener(bind_ip=args.gsmtap_bind, bind_port=args.gsmtap_port)
capture = CaptureManager(backend, db, log_interval=args.log_interval)
elif args.capture == 'direct':
backend = DirectSniffer()
@@ -78,6 +82,7 @@ def main():
print(f'simtrace-analyser-server v{__version__} — http://{args.host}:{args.port}', file=sys.stderr)
print(f' capture mode: {args.capture}', file=sys.stderr)
if args.capture == 'gsmtap':
print(f' GSMTAP bind: {args.gsmtap_bind}', file=sys.stderr)
print(f' GSMTAP port: {args.gsmtap_port}', file=sys.stderr)
print(f' database: {args.db}', file=sys.stderr)
print(f' PWA served at http://{args.host}:{args.port}/', file=sys.stderr)
+5 -3
View File
@@ -39,15 +39,17 @@ def gsmtap_msg_type(sub_type):
class GsmtapListener:
def __init__(self, bind_port=4729):
def __init__(self, bind_port=4729, bind_ip='127.0.0.1'):
self._bind_port = bind_port
self._receiver = GsmtapReceiver(bind_port=bind_port)
self._bind_ip = bind_ip
self._receiver = GsmtapReceiver(bind_ip=bind_ip, bind_port=bind_port)
self._running = False
self._dropped = 0
def start(self):
if getattr(self._receiver, '_closed', False):
self._receiver = GsmtapReceiver(bind_port=self._bind_port)
self._receiver = GsmtapReceiver(bind_ip=self._bind_ip,
bind_port=self._bind_port)
self._running = True
self._dropped = 0
+1 -1
View File
@@ -1,3 +1,3 @@
"""Single source of truth for the SIMtrace Analyser version."""
__version__ = '2.7.12'
__version__ = '2.7.13'
+35
View File
@@ -206,6 +206,41 @@ class TestGsmtapListenerRobustness(unittest.TestCase):
self.assertEqual(listener.dropped, 1)
class TestGsmtapListenerBind(unittest.TestCase):
def test_bind_ip_reaches_the_receiver(self):
from simtrace_analyser import capture
calls = []
class _FakeReceiver:
def __init__(self, bind_ip='127.0.0.1', bind_port=4729):
calls.append((bind_ip, bind_port))
def close(self):
pass
real = capture.GsmtapReceiver
capture.GsmtapReceiver = _FakeReceiver
try:
listener = capture.GsmtapListener(bind_ip='0.0.0.0', bind_port=4729)
listener._receiver._closed = True # force start() to re-create it
listener.start()
finally:
capture.GsmtapReceiver = real
self.assertEqual(calls, [('0.0.0.0', 4729), ('0.0.0.0', 4729)])
def test_all_interfaces_bind(self):
from simtrace_analyser.capture import GsmtapListener
listener = GsmtapListener(bind_ip='0.0.0.0', bind_port=0)
self.addCleanup(listener.stop)
self.assertEqual(listener._receiver._sock.getsockname()[0], '0.0.0.0')
def test_default_bind_is_loopback(self):
from simtrace_analyser.capture import GsmtapListener
listener = GsmtapListener(bind_port=0)
self.addCleanup(listener.stop)
self.assertEqual(listener._receiver._sock.getsockname()[0], '127.0.0.1')
class TestCaptureLoopRobustness(unittest.TestCase):
def test_capture_loop_survives_insert_error(self):
mgr = CaptureManager(_OneShotBackend(), _NullDb())
+9
View File
@@ -70,6 +70,15 @@ class TestDistributionMetadata(unittest.TestCase):
# The sdist carries frontend/, so a wheel built from it has the PWA.
self.assertIn('graft frontend', self._read('MANIFEST.in'))
def test_license_is_mit(self):
# The project is MIT; the metadata and the file must not drift apart.
pyproject = self._read('pyproject.toml')
self.assertIn('license = "MIT"', pyproject)
self.assertIn('license-files = ["LICENSE"]', pyproject)
license_text = self._read('LICENSE')
self.assertTrue(license_text.startswith('MIT License\n'))
self.assertIn('Permission is hereby granted, free of charge', license_text)
def test_setup_py_bundles_the_pwa(self):
setup_py = self._read('setup.py')
self.assertIn('frontend', setup_py)
+1 -1
View File
@@ -224,7 +224,7 @@ class TestImportEndpoint(unittest.TestCase):
class TestVersion(unittest.TestCase):
def test_package_version(self):
from simtrace2_pysniff import __version__
self.assertEqual(__version__, '2.7.12')
self.assertEqual(__version__, '2.7.13')
def test_status_includes_version(self):
import os
+1
View File
@@ -51,6 +51,7 @@ class TestReleaseZip(unittest.TestCase):
'sniff-start.sh',
'sniff-start.bat',
'70-simtrace2-pysniff.rules',
'LICENSE',
'README.md'):
self.assertIn('%s/%s' % (top, rel), self.names)