tunnelscope 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. tunnelscope-0.2.1/LICENSE +21 -0
  2. tunnelscope-0.2.1/MANIFEST.in +5 -0
  3. tunnelscope-0.2.1/PKG-INFO +166 -0
  4. tunnelscope-0.2.1/README.md +150 -0
  5. tunnelscope-0.2.1/docs/bidirectional.md +77 -0
  6. tunnelscope-0.2.1/docs/demo.md +67 -0
  7. tunnelscope-0.2.1/docs/images/two-vm-results.png +0 -0
  8. tunnelscope-0.2.1/docs/receiver-observations.md +55 -0
  9. tunnelscope-0.2.1/docs/releases/v0.2.1.md +24 -0
  10. tunnelscope-0.2.1/docs/v0.1-scope.md +32 -0
  11. tunnelscope-0.2.1/docs/validation/ack-return-blocked.md +25 -0
  12. tunnelscope-0.2.1/docs/validation/mtu-boundary.md +65 -0
  13. tunnelscope-0.2.1/docs/validation/pmtu-blackhole.md +54 -0
  14. tunnelscope-0.2.1/docs/validation/two-vm-field-test.md +48 -0
  15. tunnelscope-0.2.1/docs/validation/two-vm-receiver-correlation.md +50 -0
  16. tunnelscope-0.2.1/examples/mtu-boundary.json +269 -0
  17. tunnelscope-0.2.1/examples/pmtu-blackhole.json +153 -0
  18. tunnelscope-0.2.1/labs/ack_return_blocked.py +118 -0
  19. tunnelscope-0.2.1/labs/mtu_boundary.py +80 -0
  20. tunnelscope-0.2.1/labs/pmtu_blackhole.py +113 -0
  21. tunnelscope-0.2.1/pyproject.toml +28 -0
  22. tunnelscope-0.2.1/setup.cfg +4 -0
  23. tunnelscope-0.2.1/tests/test_delivery.py +213 -0
  24. tunnelscope-0.2.1/tests/test_fragmentation.py +103 -0
  25. tunnelscope-0.2.1/tunnelscope-logo-blue.png +0 -0
  26. tunnelscope-0.2.1/tunnelscope.egg-info/PKG-INFO +166 -0
  27. tunnelscope-0.2.1/tunnelscope.egg-info/SOURCES.txt +29 -0
  28. tunnelscope-0.2.1/tunnelscope.egg-info/dependency_links.txt +1 -0
  29. tunnelscope-0.2.1/tunnelscope.egg-info/entry_points.txt +2 -0
  30. tunnelscope-0.2.1/tunnelscope.egg-info/top_level.txt +1 -0
  31. tunnelscope-0.2.1/tunnelscope.py +517 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TunnelScope contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include tunnelscope-logo-blue.png
2
+ include LICENSE
3
+ recursive-include docs *.md *.png
4
+ recursive-include examples *.json
5
+ recursive-include labs *.py
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: tunnelscope
3
+ Version: 0.2.1
4
+ Summary: Evidence-based MTU and packet-size diagnostics for Linux networks
5
+ License-Expression: MIT
6
+ Project-URL: Repository, https://github.com/lazypanic999/tunnelscope
7
+ Keywords: network,mtu,pmtu,diagnostics
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ <p align="center">
18
+ <img src="https://raw.githubusercontent.com/lazypanic999/tunnelscope/main/tunnelscope-logo-blue.png" alt="TunnelScope blue compass and vortex logo" width="400">
19
+ </p>
20
+
21
+ # TunnelScope
22
+
23
+ Evidence-based MTU and packet-size diagnostics for Linux networks.
24
+
25
+ **Status: v0.2.1 alpha prototype. UDP delivery probes, Linux IPv4 Don't Fragment mode, per-direction report combination, optional receiver observations and controlled MTU labs are available. Automatic path-MTU discovery and root-cause diagnosis are not implemented.**
26
+
27
+ ## The problem
28
+
29
+ A connection can appear healthy while larger transfers fail. Small probes may succeed even when larger packets cannot traverse the path. Tunnel overhead, MTU configuration and blocked Path MTU Discovery feedback can contribute to this behavior. Other causes can produce similar symptoms.
30
+
31
+ TunnelScope aims to help network engineers, system administrators and learners investigate these failures through repeatable measurements and inspectable evidence.
32
+
33
+ ## Planned first version
34
+
35
+ - A Python command-line tool and a cooperating receiver for controlled Linux environments.
36
+ - Repeated, size-controlled IPv4 UDP probes with fragmentation disabled.
37
+ - Separate measurements for each direction, with timeouts and loss recorded explicitly.
38
+ - JSON reports containing probe sizes, outcomes, relevant interface information and test settings.
39
+ - A reproducible Linux lab demonstrating healthy delivery, explicit packet-too-large feedback and a possible PMTU black hole.
40
+
41
+ A failed probe alone will not be labeled as proof of an MTU problem. Reports separate observations, hypotheses and missing evidence. An observed working packet size is not presented as the exact path MTU.
42
+
43
+ ## Milestones
44
+
45
+ - [x] **M0 - Reproduce the problem:** build a small Linux network-namespace lab and record a healthy baseline and a controlled MTU failure.
46
+ - [x] **M1 - Probe and receive:** implement bounded, identifiable probes and acknowledgements between two endpoints.
47
+ - [x] **M2 - Explain the evidence:** export JSON and distinguish successful delivery, explicit errors and inconclusive timeouts.
48
+ - [x] **M3 - Validate:** check the tool against known lab configurations, document limitations and publish a short demo.
49
+ - [x] **M4 - Observe at the receiver:** record which identified probes reached the cooperating receiver and correlate them with sender outcomes.
50
+ - [x] **M5 - Make evidence usable:** validate receiver-observed timeouts in a controlled lab and provide a concise terminal view of reports.
51
+
52
+ Kubernetes, VPN-specific workflows, IPv6 and 5G user-plane tunnels are possible later directions. They are not currently supported features.
53
+
54
+ For a quick walkthrough of both controlled failure modes, see the [two-minute Linux demo](https://github.com/lazypanic999/tunnelscope/blob/main/docs/demo.md).
55
+ The [two-VM validation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/two-vm-field-test.md) documents a successful bidirectional DF run between Ubuntu and Debian guests.
56
+ The [v0.1 scope](https://github.com/lazypanic999/tunnelscope/blob/main/docs/v0.1-scope.md) lists what the installable prototype does and does not cover.
57
+ The [v0.2 receiver workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/receiver-observations.md) explains how to capture and correlate receiver evidence.
58
+ The [two-VM correlation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/two-vm-receiver-correlation.md) documents the new workflow on independent Linux guests.
59
+ The [blocked ACK return lab](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/ack-return-blocked.md) validates a receiver-observed sender timeout.
60
+
61
+ Use `tunnelscope show report.json` for a short terminal table of a probe,
62
+ bidirectional comparison, correlation, or lab report. JSON remains the
63
+ machine-readable source of evidence.
64
+
65
+ ## Install locally
66
+
67
+ From the repository root, in a Python 3.10+ virtual environment:
68
+
69
+ ```sh
70
+ python3 -m pip install .
71
+ tunnelscope --help
72
+ ```
73
+
74
+ The package installs the `tunnelscope` command. It has no third-party runtime dependencies. Published releases can be installed with `python3 -m pip install tunnelscope`. The laboratory scripts still run from a source checkout.
75
+
76
+ ## Getting started
77
+
78
+ Requires Python 3.10 or newer. No third-party runtime packages are needed. Start with a local experiment in two terminals, from the repository directory. Use `tunnelscope` in place of `python tunnelscope.py` after installing the package.
79
+
80
+ Terminal 1 (receiver):
81
+
82
+ ```sh
83
+ python tunnelscope.py receive
84
+ ```
85
+
86
+ Terminal 2 (sender):
87
+
88
+ ```sh
89
+ python tunnelscope.py probe 127.0.0.1 --sizes 64,512,1200,1400 --count 3 --output report.json
90
+ ```
91
+
92
+ The receiver exits after 100 datagrams or 30 seconds without a datagram. Restart it if it has already exited. `--max-packets` and `--idle-timeout` change these limits.
93
+
94
+ Probe sizes are **UDP payload bytes**, including TunnelScope's 24-byte header. They exclude IP and UDP headers. The receiver sends only a small acknowledgement, so this is not a same-sized round-trip test. Reverse endpoint roles to investigate delivery in the other direction.
95
+
96
+ Each probe report includes a `source` snapshot taken from the first connected sender socket. It records the local IPv4 address. On Linux, it also records the selected interface and its link MTU when discoverable, plus `kernel_route_mtu_bytes` when the kernel supplies it. The link MTU and kernel route MTU are different facts; neither is an independently measured end-to-end path MTU. A failed connection may leave `source` as `null`, and optional fields may be absent.
97
+
98
+ `acknowledged` means that the receiver got that payload and its acknowledgement returned. `timeout` means no matching acknowledgement arrived before the deadline; it does not prove an MTU issue. `socket_error` records a socket error. Without `--dont-fragment`, fragmentation follows OS defaults; the tool does not force it on or off. The CLI exits successfully when it produces a report; inspect per-probe statuses for delivery outcomes.
99
+
100
+ JSON reports also include `summary.by_payload_size`. Each entry counts `acknowledged`, `packet_too_large`, `timeout` and `socket_error` attempts separately. Its `assessment` is `delivery_observed`, `kernel_rejected_df_payload`, `inconclusive`, `socket_error` or `mixed_observations`. Repeated payload sizes share one entry, while individual attempts remain in `results`. The summary does not infer an exact path MTU or turn a timeout into a diagnosis.
101
+
102
+ The `evidence` section lists counted observations, tentative hypotheses and missing checks separately. A clean pattern where every acknowledged size is below every timed-out size suggests a possible size-related delivery problem; in DF mode it also lists a possible PMTU-feedback black hole. Filtering and ordinary loss remain alternative explanations. Mixed or reversed size patterns produce no such hypothesis. Missing evidence calls out same-sized reverse traffic, receiver/path observation for timeouts, ICMP feedback visibility in DF mode and fragmentation control when DF is off. These are prompts for investigation, not automatic root-cause findings.
103
+
104
+ To compare directions, run `probe` once from A to B and once from B to A, then combine the two JSON files:
105
+
106
+ ```sh
107
+ python tunnelscope.py combine a-to-b.json b-to-a.json --output bidirectional.json
108
+ ```
109
+
110
+ The command preserves the two reports and aligns per-size assessments; it does not initiate a remote measurement. See the [two-endpoint workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/bidirectional.md) for the receiver and sender steps. `endpoint_check` compares the recorded IPv4 addresses but cannot authenticate endpoints or account for address translation.
111
+
112
+ ### Linux IPv4 Don't Fragment mode
113
+
114
+ ```sh
115
+ python tunnelscope.py probe 192.0.2.2 --sizes 64,1252,1253,1400 --count 1 --dont-fragment --output report.json
116
+ ```
117
+
118
+ Use the address of your lab receiver; `192.0.2.2` is an example. This mode sets `IP_MTU_DISCOVER=IP_PMTUDISC_DO` on each sender socket. The kernel sets DF and rejects datagrams above its currently known path MTU. It does not bypass cached PMTU information or search automatically for a boundary. The flag requires Linux, including WSL2. Other platforms reject it; socket-option errors stop the experiment rather than silently reverting to defaults.
119
+
120
+ In DF mode, `EMSGSIZE` is reported as `packet_too_large`, with its numeric `errno`. When available, `kernel_reported_mtu_bytes` contains the connected socket's `IP_MTU` value. That value is kernel knowledge, not an independently measured end-to-end MTU. Timeouts remain inconclusive. The JSON schema version remains 1 with additive fields and the new `packet_too_large` status; consumers must handle it explicitly.
121
+
122
+ ### Reproduce a known MTU boundary
123
+
124
+ On Linux with root access and iproute2 installed:
125
+
126
+ ```sh
127
+ sudo python3 labs/mtu_boundary.py > mtu-boundary.json
128
+ ```
129
+
130
+ The lab creates two temporary network namespaces connected by a veth pair with MTU 1280. It probes both directions and checks that 1252-byte UDP payloads succeed while 1253-byte payloads fail in DF mode (20-byte IPv4 header + 8-byte UDP header). It changes no host interface MTU or host route and removes its namespaces on completion or ordinary interruption. As with any process, forced termination such as SIGKILL can prevent cleanup.
131
+
132
+ This validates a directly attached link boundary, not an unknown multi-hop bottleneck or a PMTU black hole. See the [validation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/mtu-boundary.md) and [captured example report](https://github.com/lazypanic999/tunnelscope/blob/main/examples/mtu-boundary.json).
133
+
134
+ ### Reproduce blocked PMTU feedback
135
+
136
+ On Linux with root access, iproute2, iptables and sysctl installed:
137
+
138
+ ```sh
139
+ sudo python3 labs/pmtu_blackhole.py > pmtu-blackhole.json
140
+ ```
141
+
142
+ This lab creates a sender, router and receiver in temporary network namespaces. The sender-facing link has MTU 1500; the router's receiver-facing link has MTU 1280. The router drops only its outgoing ICMP fragmentation-needed messages. In DF mode, 64- and 1252-byte UDP payloads are acknowledged, while 1253- and 1400-byte payloads time out. The lab changes no host routes, interfaces or firewall rules and removes its namespaces on ordinary completion or interruption. Forced termination such as SIGKILL can prevent cleanup.
143
+
144
+ The known lab topology explains these timeouts; a timeout on an unknown network remains inconclusive. See the [validation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/pmtu-blackhole.md) and [captured report](https://github.com/lazypanic999/tunnelscope/blob/main/examples/pmtu-blackhole.json). This lab demonstrates one controlled failure mode; it does not establish automatic black-hole detection.
145
+
146
+ The receiver binds to loopback by default. For your own two-host lab, use `--bind <receiver-lab-IPv4>` and probe that address. The experimental protocol is unencrypted and unauthenticated; use a controlled lab, not a public service.
147
+
148
+ Run checks:
149
+
150
+ ```sh
151
+ python -m unittest discover -s tests -v
152
+ ```
153
+
154
+ Privileged Linux integration check (also run by CI on Ubuntu):
155
+
156
+ ```sh
157
+ sudo env TUNNELSCOPE_RUN_NETNS_TESTS=1 python3 -m unittest discover -s tests -v
158
+ ```
159
+
160
+ ## Prior art and scope
161
+
162
+ Existing tools such as ping, tracepath and packet analyzers already provide important building blocks. TunnelScope will explore whether controlled two-endpoint tests and evidence-oriented reporting make this investigation easier. It is not a replacement for a full packet analyzer.
163
+
164
+ ## Contributing
165
+
166
+ Early feedback is welcome through GitHub Issues, especially reproducible examples where small traffic succeeds and larger transfers fail. Describe the environment, expected behavior and observed behavior. Use synthetic or sanitized examples; do not attach credentials or private packet captures.
@@ -0,0 +1,150 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/lazypanic999/tunnelscope/main/tunnelscope-logo-blue.png" alt="TunnelScope blue compass and vortex logo" width="400">
3
+ </p>
4
+
5
+ # TunnelScope
6
+
7
+ Evidence-based MTU and packet-size diagnostics for Linux networks.
8
+
9
+ **Status: v0.2.1 alpha prototype. UDP delivery probes, Linux IPv4 Don't Fragment mode, per-direction report combination, optional receiver observations and controlled MTU labs are available. Automatic path-MTU discovery and root-cause diagnosis are not implemented.**
10
+
11
+ ## The problem
12
+
13
+ A connection can appear healthy while larger transfers fail. Small probes may succeed even when larger packets cannot traverse the path. Tunnel overhead, MTU configuration and blocked Path MTU Discovery feedback can contribute to this behavior. Other causes can produce similar symptoms.
14
+
15
+ TunnelScope aims to help network engineers, system administrators and learners investigate these failures through repeatable measurements and inspectable evidence.
16
+
17
+ ## Planned first version
18
+
19
+ - A Python command-line tool and a cooperating receiver for controlled Linux environments.
20
+ - Repeated, size-controlled IPv4 UDP probes with fragmentation disabled.
21
+ - Separate measurements for each direction, with timeouts and loss recorded explicitly.
22
+ - JSON reports containing probe sizes, outcomes, relevant interface information and test settings.
23
+ - A reproducible Linux lab demonstrating healthy delivery, explicit packet-too-large feedback and a possible PMTU black hole.
24
+
25
+ A failed probe alone will not be labeled as proof of an MTU problem. Reports separate observations, hypotheses and missing evidence. An observed working packet size is not presented as the exact path MTU.
26
+
27
+ ## Milestones
28
+
29
+ - [x] **M0 - Reproduce the problem:** build a small Linux network-namespace lab and record a healthy baseline and a controlled MTU failure.
30
+ - [x] **M1 - Probe and receive:** implement bounded, identifiable probes and acknowledgements between two endpoints.
31
+ - [x] **M2 - Explain the evidence:** export JSON and distinguish successful delivery, explicit errors and inconclusive timeouts.
32
+ - [x] **M3 - Validate:** check the tool against known lab configurations, document limitations and publish a short demo.
33
+ - [x] **M4 - Observe at the receiver:** record which identified probes reached the cooperating receiver and correlate them with sender outcomes.
34
+ - [x] **M5 - Make evidence usable:** validate receiver-observed timeouts in a controlled lab and provide a concise terminal view of reports.
35
+
36
+ Kubernetes, VPN-specific workflows, IPv6 and 5G user-plane tunnels are possible later directions. They are not currently supported features.
37
+
38
+ For a quick walkthrough of both controlled failure modes, see the [two-minute Linux demo](https://github.com/lazypanic999/tunnelscope/blob/main/docs/demo.md).
39
+ The [two-VM validation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/two-vm-field-test.md) documents a successful bidirectional DF run between Ubuntu and Debian guests.
40
+ The [v0.1 scope](https://github.com/lazypanic999/tunnelscope/blob/main/docs/v0.1-scope.md) lists what the installable prototype does and does not cover.
41
+ The [v0.2 receiver workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/receiver-observations.md) explains how to capture and correlate receiver evidence.
42
+ The [two-VM correlation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/two-vm-receiver-correlation.md) documents the new workflow on independent Linux guests.
43
+ The [blocked ACK return lab](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/ack-return-blocked.md) validates a receiver-observed sender timeout.
44
+
45
+ Use `tunnelscope show report.json` for a short terminal table of a probe,
46
+ bidirectional comparison, correlation, or lab report. JSON remains the
47
+ machine-readable source of evidence.
48
+
49
+ ## Install locally
50
+
51
+ From the repository root, in a Python 3.10+ virtual environment:
52
+
53
+ ```sh
54
+ python3 -m pip install .
55
+ tunnelscope --help
56
+ ```
57
+
58
+ The package installs the `tunnelscope` command. It has no third-party runtime dependencies. Published releases can be installed with `python3 -m pip install tunnelscope`. The laboratory scripts still run from a source checkout.
59
+
60
+ ## Getting started
61
+
62
+ Requires Python 3.10 or newer. No third-party runtime packages are needed. Start with a local experiment in two terminals, from the repository directory. Use `tunnelscope` in place of `python tunnelscope.py` after installing the package.
63
+
64
+ Terminal 1 (receiver):
65
+
66
+ ```sh
67
+ python tunnelscope.py receive
68
+ ```
69
+
70
+ Terminal 2 (sender):
71
+
72
+ ```sh
73
+ python tunnelscope.py probe 127.0.0.1 --sizes 64,512,1200,1400 --count 3 --output report.json
74
+ ```
75
+
76
+ The receiver exits after 100 datagrams or 30 seconds without a datagram. Restart it if it has already exited. `--max-packets` and `--idle-timeout` change these limits.
77
+
78
+ Probe sizes are **UDP payload bytes**, including TunnelScope's 24-byte header. They exclude IP and UDP headers. The receiver sends only a small acknowledgement, so this is not a same-sized round-trip test. Reverse endpoint roles to investigate delivery in the other direction.
79
+
80
+ Each probe report includes a `source` snapshot taken from the first connected sender socket. It records the local IPv4 address. On Linux, it also records the selected interface and its link MTU when discoverable, plus `kernel_route_mtu_bytes` when the kernel supplies it. The link MTU and kernel route MTU are different facts; neither is an independently measured end-to-end path MTU. A failed connection may leave `source` as `null`, and optional fields may be absent.
81
+
82
+ `acknowledged` means that the receiver got that payload and its acknowledgement returned. `timeout` means no matching acknowledgement arrived before the deadline; it does not prove an MTU issue. `socket_error` records a socket error. Without `--dont-fragment`, fragmentation follows OS defaults; the tool does not force it on or off. The CLI exits successfully when it produces a report; inspect per-probe statuses for delivery outcomes.
83
+
84
+ JSON reports also include `summary.by_payload_size`. Each entry counts `acknowledged`, `packet_too_large`, `timeout` and `socket_error` attempts separately. Its `assessment` is `delivery_observed`, `kernel_rejected_df_payload`, `inconclusive`, `socket_error` or `mixed_observations`. Repeated payload sizes share one entry, while individual attempts remain in `results`. The summary does not infer an exact path MTU or turn a timeout into a diagnosis.
85
+
86
+ The `evidence` section lists counted observations, tentative hypotheses and missing checks separately. A clean pattern where every acknowledged size is below every timed-out size suggests a possible size-related delivery problem; in DF mode it also lists a possible PMTU-feedback black hole. Filtering and ordinary loss remain alternative explanations. Mixed or reversed size patterns produce no such hypothesis. Missing evidence calls out same-sized reverse traffic, receiver/path observation for timeouts, ICMP feedback visibility in DF mode and fragmentation control when DF is off. These are prompts for investigation, not automatic root-cause findings.
87
+
88
+ To compare directions, run `probe` once from A to B and once from B to A, then combine the two JSON files:
89
+
90
+ ```sh
91
+ python tunnelscope.py combine a-to-b.json b-to-a.json --output bidirectional.json
92
+ ```
93
+
94
+ The command preserves the two reports and aligns per-size assessments; it does not initiate a remote measurement. See the [two-endpoint workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/bidirectional.md) for the receiver and sender steps. `endpoint_check` compares the recorded IPv4 addresses but cannot authenticate endpoints or account for address translation.
95
+
96
+ ### Linux IPv4 Don't Fragment mode
97
+
98
+ ```sh
99
+ python tunnelscope.py probe 192.0.2.2 --sizes 64,1252,1253,1400 --count 1 --dont-fragment --output report.json
100
+ ```
101
+
102
+ Use the address of your lab receiver; `192.0.2.2` is an example. This mode sets `IP_MTU_DISCOVER=IP_PMTUDISC_DO` on each sender socket. The kernel sets DF and rejects datagrams above its currently known path MTU. It does not bypass cached PMTU information or search automatically for a boundary. The flag requires Linux, including WSL2. Other platforms reject it; socket-option errors stop the experiment rather than silently reverting to defaults.
103
+
104
+ In DF mode, `EMSGSIZE` is reported as `packet_too_large`, with its numeric `errno`. When available, `kernel_reported_mtu_bytes` contains the connected socket's `IP_MTU` value. That value is kernel knowledge, not an independently measured end-to-end MTU. Timeouts remain inconclusive. The JSON schema version remains 1 with additive fields and the new `packet_too_large` status; consumers must handle it explicitly.
105
+
106
+ ### Reproduce a known MTU boundary
107
+
108
+ On Linux with root access and iproute2 installed:
109
+
110
+ ```sh
111
+ sudo python3 labs/mtu_boundary.py > mtu-boundary.json
112
+ ```
113
+
114
+ The lab creates two temporary network namespaces connected by a veth pair with MTU 1280. It probes both directions and checks that 1252-byte UDP payloads succeed while 1253-byte payloads fail in DF mode (20-byte IPv4 header + 8-byte UDP header). It changes no host interface MTU or host route and removes its namespaces on completion or ordinary interruption. As with any process, forced termination such as SIGKILL can prevent cleanup.
115
+
116
+ This validates a directly attached link boundary, not an unknown multi-hop bottleneck or a PMTU black hole. See the [validation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/mtu-boundary.md) and [captured example report](https://github.com/lazypanic999/tunnelscope/blob/main/examples/mtu-boundary.json).
117
+
118
+ ### Reproduce blocked PMTU feedback
119
+
120
+ On Linux with root access, iproute2, iptables and sysctl installed:
121
+
122
+ ```sh
123
+ sudo python3 labs/pmtu_blackhole.py > pmtu-blackhole.json
124
+ ```
125
+
126
+ This lab creates a sender, router and receiver in temporary network namespaces. The sender-facing link has MTU 1500; the router's receiver-facing link has MTU 1280. The router drops only its outgoing ICMP fragmentation-needed messages. In DF mode, 64- and 1252-byte UDP payloads are acknowledged, while 1253- and 1400-byte payloads time out. The lab changes no host routes, interfaces or firewall rules and removes its namespaces on ordinary completion or interruption. Forced termination such as SIGKILL can prevent cleanup.
127
+
128
+ The known lab topology explains these timeouts; a timeout on an unknown network remains inconclusive. See the [validation record](https://github.com/lazypanic999/tunnelscope/blob/main/docs/validation/pmtu-blackhole.md) and [captured report](https://github.com/lazypanic999/tunnelscope/blob/main/examples/pmtu-blackhole.json). This lab demonstrates one controlled failure mode; it does not establish automatic black-hole detection.
129
+
130
+ The receiver binds to loopback by default. For your own two-host lab, use `--bind <receiver-lab-IPv4>` and probe that address. The experimental protocol is unencrypted and unauthenticated; use a controlled lab, not a public service.
131
+
132
+ Run checks:
133
+
134
+ ```sh
135
+ python -m unittest discover -s tests -v
136
+ ```
137
+
138
+ Privileged Linux integration check (also run by CI on Ubuntu):
139
+
140
+ ```sh
141
+ sudo env TUNNELSCOPE_RUN_NETNS_TESTS=1 python3 -m unittest discover -s tests -v
142
+ ```
143
+
144
+ ## Prior art and scope
145
+
146
+ Existing tools such as ping, tracepath and packet analyzers already provide important building blocks. TunnelScope will explore whether controlled two-endpoint tests and evidence-oriented reporting make this investigation easier. It is not a replacement for a full packet analyzer.
147
+
148
+ ## Contributing
149
+
150
+ Early feedback is welcome through GitHub Issues, especially reproducible examples where small traffic succeeds and larger transfers fail. Describe the environment, expected behavior and observed behavior. Use synthetic or sanitized examples; do not attach credentials or private packet captures.
@@ -0,0 +1,77 @@
1
+ # Measure both directions
2
+
3
+ TunnelScope's receiver sends a small acknowledgement. One `probe` run therefore
4
+ measures delivery of the requested payload in one direction, plus return of the
5
+ small acknowledgement. To measure the reverse direction, the endpoints must
6
+ exchange sender and receiver roles. This workflow is for a controlled Linux
7
+ environment where both sides can run Python 3.10+ and reach each other over UDP.
8
+
9
+ Use the actual endpoint IPv4 addresses in place of `A_IP` and `B_IP`. Start each
10
+ receiver shortly before its probe; by default it exits after 30 seconds idle.
11
+
12
+ ## A to B
13
+
14
+ On endpoint B:
15
+
16
+ ```sh
17
+ python3 tunnelscope.py receive --bind B_IP
18
+ ```
19
+
20
+ On endpoint A:
21
+
22
+ ```sh
23
+ python3 tunnelscope.py probe B_IP --sizes 64,1252,1253,1400 --count 3 --dont-fragment --output a-to-b.json
24
+ ```
25
+
26
+ ## B to A
27
+
28
+ On endpoint A:
29
+
30
+ ```sh
31
+ python3 tunnelscope.py receive --bind A_IP
32
+ ```
33
+
34
+ On endpoint B:
35
+
36
+ ```sh
37
+ python3 tunnelscope.py probe A_IP --sizes 64,1252,1253,1400 --count 3 --dont-fragment --output b-to-a.json
38
+ ```
39
+
40
+ Copy `b-to-a.json` to the machine holding `a-to-b.json`. Then run:
41
+
42
+ ```sh
43
+ python3 tunnelscope.py combine a-to-b.json b-to-a.json --output bidirectional.json
44
+ ```
45
+
46
+ `bidirectional.json` contains the unmodified probe reports under `directions`,
47
+ with `a-to-b` and `b-to-a` labels supplied by the command order. Its
48
+ `comparison.by_payload_size` aligns each direction's assessment for the same
49
+ UDP payload size. A missing size has a `null` assessment. The command does not
50
+ turn two timeouts into a diagnosis.
51
+ Each embedded report's `evidence` describes that one run; `combine` preserves
52
+ it without retroactively treating the other file as verified evidence.
53
+
54
+ `endpoint_check` is `reversed_ipv4_match` when both recorded source IPv4
55
+ addresses equal the opposite report's target address. It is
56
+ `ipv4_mismatch_or_translation` when they differ, or `unavailable` if either
57
+ source snapshot is missing. NAT, multiple local addresses and user-selected
58
+ files can affect this check; the command cannot verify that the files came
59
+ from the intended endpoints.
60
+
61
+ ## Source fields
62
+
63
+ `source.ipv4` is the first connected sender socket's local IPv4 address. On
64
+ Linux, `source.interface` and `source.interface_mtu_bytes` are included when
65
+ the address can be matched to a local interface. `source.kernel_route_mtu_bytes`
66
+ is the kernel's current MTU value for that connected route when available. A
67
+ link MTU is local interface configuration; a kernel route MTU can reflect
68
+ cached path knowledge. Neither field alone proves the end-to-end path MTU.
69
+
70
+ The report contains a source snapshot, not a live record of route changes
71
+ during later attempts. When a socket cannot connect, `source` can be `null`.
72
+ The probe protocol is unauthenticated and unencrypted; use a controlled lab.
73
+
74
+ For each direction, you can also save observations at the receiving endpoint
75
+ and use `tunnelscope correlate` to check which individual probes arrived. See
76
+ the [receiver observation workflow](receiver-observations.md). This provides
77
+ different evidence from a reverse-direction probe.
@@ -0,0 +1,67 @@
1
+ # TunnelScope: two-minute Linux demo
2
+
3
+ Run this from the repository root on Linux with root access, Python 3.10+,
4
+ iproute2, iptables and sysctl. The labs create temporary network namespaces;
5
+ they do not change host interfaces, routes or firewall rules. Forced termination
6
+ such as SIGKILL can prevent namespace cleanup.
7
+
8
+ ## 1. Run two controlled experiments
9
+
10
+ ```sh
11
+ sudo python3 labs/mtu_boundary.py > boundary.json
12
+ sudo python3 labs/pmtu_blackhole.py > blackhole.json
13
+ ```
14
+
15
+ The first experiment uses a direct 1280-byte link. The second routes traffic
16
+ from a 1500-byte link onto a 1280-byte link while blocking the router's ICMP
17
+ fragmentation-needed feedback.
18
+
19
+ ## 2. Compare what the sender observed
20
+
21
+ ```sh
22
+ python3 - <<'PY'
23
+ import json
24
+
25
+ with open("boundary.json", encoding="utf-8") as source:
26
+ boundary = json.load(source)
27
+ with open("blackhole.json", encoding="utf-8") as source:
28
+ blackhole = json.load(source)
29
+
30
+ def show(label, results):
31
+ statuses = ", ".join(f"{row['payload_bytes']}={row['status']}" for row in results)
32
+ print(f"{label}: {statuses}")
33
+
34
+ for direction in boundary["directions"]:
35
+ show(direction["direction"], direction["results"])
36
+ show("blocked ICMP", blackhole["probe"]["results"])
37
+ print("blocked feedback packets:", blackhole["blocked_feedback_packets"])
38
+ PY
39
+ ```
40
+
41
+ Example output (statuses are checked by the labs; the firewall counter can vary):
42
+
43
+ ```text
44
+ a-to-b: 64=acknowledged, 1252=acknowledged, 1253=packet_too_large, 1400=packet_too_large
45
+ b-to-a: 64=acknowledged, 1252=acknowledged, 1253=packet_too_large, 1400=packet_too_large
46
+ blocked ICMP: 64=acknowledged, 1252=acknowledged, 1253=timeout, 1400=timeout
47
+ blocked feedback packets: 2
48
+ ```
49
+
50
+ The direct-link sender receives a local `EMSGSIZE` error above 1252 UDP payload
51
+ bytes. In the routed lab, the large DF packets cannot cross the 1280-byte
52
+ egress link, and the router's ICMP feedback is blocked. The sender sees only
53
+ timeouts. The lab knows why; an unknown network path would require more
54
+ evidence before calling a timeout a PMTU black hole.
55
+
56
+ ## 3. Inspect the JSON evidence
57
+
58
+ Each probe report keeps individual attempts in `results`, counts outcomes
59
+ per payload size in `summary.by_payload_size`, and separates observations,
60
+ possible explanations and missing checks in `evidence`. The routed lab also records
61
+ `blocked_feedback_packets` from its isolated firewall rule. Compare your run
62
+ with the [direct-link example](../examples/mtu-boundary.json) and the
63
+ [blocked-feedback example](../examples/pmtu-blackhole.json).
64
+
65
+ The validation records explain the [direct-link boundary](validation/mtu-boundary.md)
66
+ and [routed black-hole scenario](validation/pmtu-blackhole.md). These labs do
67
+ not validate arbitrary networks, VPNs, IPv6 or same-sized return traffic.
@@ -0,0 +1,55 @@
1
+ # Correlate receiver observations
2
+
3
+ In v0.2, a cooperating receiver can save the probe IDs and payload sizes it
4
+ observed. The sender also includes each 128-bit random probe ID in its JSON
5
+ results. From v0.2.1, the sender puts one random run ID in every payload, and
6
+ both reports record it. Correlation answers whether a particular probe reached
7
+ the receiver and whether the receiver attempted to send an acknowledgement.
8
+
9
+ Start the receiver on endpoint B and give it an output path:
10
+
11
+ ```sh
12
+ tunnelscope receive --bind B_IP --output receiver.json
13
+ ```
14
+
15
+ Run the sender on endpoint A:
16
+
17
+ ```sh
18
+ tunnelscope probe B_IP --sizes 64,1252,1253,1400 --count 3 --dont-fragment --output a-to-b.json
19
+ ```
20
+
21
+ After the sender finishes, stop the receiver with Ctrl-C or wait for its idle
22
+ timeout. The receiver writes `receiver.json` when it exits. Copy it to the
23
+ machine holding `a-to-b.json`, then run:
24
+
25
+ ```sh
26
+ tunnelscope correlate a-to-b.json receiver.json --output correlation.json
27
+ ```
28
+
29
+ `correlation.json` has one row per sender attempt. `receiver_observed: true`
30
+ confirms that the matching payload arrived at this receiver. A timeout with
31
+ `receiver_observed: true` narrows the investigation toward acknowledgement
32
+ transmission or return delivery, but does not identify which failed. A successful
33
+ acknowledgement send call only confirms that the local socket accepted it.
34
+
35
+ `receiver_observed: false` is inconclusive unless the receiver was running for
36
+ the full probe period and its report is complete. A packet may have been lost,
37
+ filtered, sent to another endpoint or missed because capture started late.
38
+ `unmatched_receiver_observations` counts valid probes seen by the receiver that
39
+ were not present in the sender file. It can reveal a mixed or incorrect pairing.
40
+
41
+ `pairing.run_id_check` is `matched` when the receiver observed this sender run.
42
+ The command rejects a receiver file containing only observations from another
43
+ run. An empty receiver file cannot establish which run it covered and is marked
44
+ `unverified_no_receiver_observations`. `pairing.endpoint_checks` compares the
45
+ target port and, unless the receiver bound to `0.0.0.0`, the target IPv4 address.
46
+ Mismatches need review; address translation can legitimately change them.
47
+ The two UTC capture windows and the receiver's `exit_reason` help review
48
+ coverage, but the command does not compare clocks across hosts because they
49
+ may be unsynchronized.
50
+
51
+ The reports are unauthenticated. Use them only in a controlled environment and
52
+ pair the files from the same run. Reports created before v0.2 do not contain
53
+ probe IDs and cannot be correlated. v0.2 reports can still be correlated by
54
+ probe ID, but their run ID check is unavailable. Receiver observation is complementary to
55
+ running probes in the reverse direction; it does not replace that test.
@@ -0,0 +1,24 @@
1
+ # TunnelScope v0.2.1
2
+
3
+ First public alpha release of the Python command-line prototype.
4
+
5
+ ## Highlights
6
+
7
+ - Bounded IPv4 UDP probes with optional Linux Don't Fragment mode, JSON evidence,
8
+ and separate per-direction comparison.
9
+ - Receiver observation reports and `correlate` for matching individual probes.
10
+ Run IDs and endpoint checks help catch incorrectly paired reports.
11
+ - `show` prints concise terminal tables for probe, correlation, bidirectional
12
+ comparison and lab JSON reports.
13
+ - Reproducible Linux namespace labs for a direct MTU boundary, blocked PMTU
14
+ feedback and a blocked acknowledgement return.
15
+ - Two-VM validation of successful delivery and receiver correlation in both
16
+ directions.
17
+
18
+ ## Known limits
19
+
20
+ Timeouts remain inconclusive without cooperating receiver or path evidence.
21
+ The CLI does not discover an exact end-to-end path MTU or prove a root cause on
22
+ an unknown network. The UDP protocol is unauthenticated and unencrypted; run
23
+ it only in a controlled environment. IPv6, VPN-specific, Kubernetes and 5G
24
+ workflows are not implemented.
@@ -0,0 +1,32 @@
1
+ # TunnelScope v0.1 scope
2
+
3
+ v0.1 is an installable prototype of the existing Python CLI. It can be built
4
+ and installed locally; it is not yet published to a package index.
5
+
6
+ ## Included
7
+
8
+ - A `tunnelscope` console command with `receive`, `probe` and `combine`.
9
+ - Bounded IPv4 UDP payload probes and small acknowledgements between controlled
10
+ endpoints.
11
+ - Optional Linux Don't Fragment mode and explicit local `EMSGSIZE` reporting.
12
+ - JSON reports with per-size counts, source/interface snapshot, observations,
13
+ tentative hypotheses and missing evidence.
14
+ - Separate direction reports that can be combined without treating either
15
+ direction as proof of the other's behavior.
16
+ - Direct-link MTU boundary and blocked-feedback Linux namespace labs.
17
+ - A two-VM bidirectional delivery validation record.
18
+
19
+ ## Outside v0.1
20
+
21
+ - Automatic discovery of an exact end-to-end path MTU or root cause.
22
+ - Automatic remote orchestration, authentication or encryption.
23
+ - IPv6, VPN-specific, Kubernetes or 5G tunnel workflows.
24
+ - A graphical interface and a published package-index release.
25
+
26
+ ## Before a public release
27
+
28
+ The local wheel and source distribution must build and install cleanly on
29
+ supported Python versions. We also need to choose and add a project license,
30
+ confirm the package name and publish a release only after reviewing the
31
+ distribution metadata and contents. These decisions are separate from local
32
+ v0.1 packaging.
@@ -0,0 +1,25 @@
1
+ # Blocked acknowledgement return validation
2
+
3
+ Run the controlled lab as root on Linux with iproute2 and iptables:
4
+
5
+ ```sh
6
+ sudo python3 labs/ack_return_blocked.py > ack-return-blocked.json
7
+ python3 tunnelscope.py show ack-return-blocked.json
8
+ ```
9
+
10
+ The lab creates two temporary network namespaces on a 1500-byte direct link.
11
+ The receiver observes two UDP probes and sends small acknowledgements. A rule
12
+ in the sender namespace drops only incoming UDP packets from the receiver's
13
+ port. The lab changes no host firewall rule, route or interface and removes
14
+ the namespaces when it exits normally.
15
+
16
+ In the validated run, both 64- and 1400-byte probes timed out at the sender.
17
+ The receiver recorded both probes and both local acknowledgement sends. The
18
+ correlation reported `timed_out_but_receiver_observed: 2`, a matched run ID,
19
+ and zero unmatched receiver observations. The isolated firewall counted two
20
+ blocked acknowledgements.
21
+
22
+ This establishes the cause only inside the known lab topology. On an unknown
23
+ network, the same sender/receiver pattern narrows the investigation to the
24
+ acknowledgement path but does not identify a failing hop or rule. Forced
25
+ termination such as SIGKILL can prevent namespace cleanup.