tunnelscope 0.2.2__tar.gz → 0.4.0__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 (46) hide show
  1. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/MANIFEST.in +1 -0
  2. {tunnelscope-0.2.2/tunnelscope.egg-info → tunnelscope-0.4.0}/PKG-INFO +74 -22
  3. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/README.md +73 -21
  4. tunnelscope-0.4.0/docs/environments.md +67 -0
  5. tunnelscope-0.4.0/docs/images/report-ui.png +0 -0
  6. tunnelscope-0.4.0/docs/ipv6.md +37 -0
  7. tunnelscope-0.4.0/docs/releases/v0.3.0.md +25 -0
  8. tunnelscope-0.4.0/docs/releases/v0.4.0.md +22 -0
  9. tunnelscope-0.4.0/docs/report-ui.md +22 -0
  10. tunnelscope-0.4.0/docs/size-sweep.md +29 -0
  11. tunnelscope-0.4.0/docs/validation/two-vm-size-sweep.md +44 -0
  12. tunnelscope-0.4.0/labs/ipv6_boundary.py +102 -0
  13. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/labs/mtu_boundary.py +10 -0
  14. tunnelscope-0.4.0/labs/vxlan_boundary.py +108 -0
  15. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/pyproject.toml +6 -2
  16. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/tests/test_delivery.py +133 -0
  17. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/tests/test_fragmentation.py +48 -0
  18. {tunnelscope-0.2.2 → tunnelscope-0.4.0/tunnelscope.egg-info}/PKG-INFO +74 -22
  19. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/tunnelscope.egg-info/SOURCES.txt +14 -1
  20. tunnelscope-0.4.0/tunnelscope.egg-info/top_level.txt +3 -0
  21. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/tunnelscope.py +133 -42
  22. tunnelscope-0.4.0/tunnelscope_assets/__init__.py +1 -0
  23. tunnelscope-0.4.0/tunnelscope_assets/tunnelscope-logo-blue.png +0 -0
  24. tunnelscope-0.4.0/tunnelscope_view.py +145 -0
  25. tunnelscope-0.2.2/tunnelscope.egg-info/top_level.txt +0 -1
  26. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/LICENSE +0 -0
  27. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/bidirectional.md +0 -0
  28. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/demo.md +0 -0
  29. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/images/two-vm-results.png +0 -0
  30. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/receiver-observations.md +0 -0
  31. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/releases/v0.2.1.md +0 -0
  32. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/releases/v0.2.2.md +0 -0
  33. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/v0.1-scope.md +0 -0
  34. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/validation/ack-return-blocked.md +0 -0
  35. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/validation/mtu-boundary.md +0 -0
  36. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/validation/pmtu-blackhole.md +0 -0
  37. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/validation/two-vm-field-test.md +0 -0
  38. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/docs/validation/two-vm-receiver-correlation.md +0 -0
  39. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/examples/mtu-boundary.json +0 -0
  40. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/examples/pmtu-blackhole.json +0 -0
  41. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/labs/ack_return_blocked.py +0 -0
  42. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/labs/pmtu_blackhole.py +0 -0
  43. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/setup.cfg +0 -0
  44. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/tunnelscope-logo-blue.png +0 -0
  45. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/tunnelscope.egg-info/dependency_links.txt +0 -0
  46. {tunnelscope-0.2.2 → tunnelscope-0.4.0}/tunnelscope.egg-info/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  include tunnelscope-logo-blue.png
2
+ recursive-include tunnelscope_assets *.png
2
3
  include LICENSE
3
4
  recursive-include docs *.md *.png
4
5
  recursive-include examples *.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunnelscope
3
- Version: 0.2.2
3
+ Version: 0.4.0
4
4
  Summary: Evidence-based MTU and packet-size diagnostics for Linux networks
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/lazypanic999/tunnelscope
@@ -22,7 +22,48 @@ Dynamic: license-file
22
22
 
23
23
  Evidence-based MTU and packet-size diagnostics for Linux networks.
24
24
 
25
- **Status: v0.2.2 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.**
25
+ **Status: v0.4.0 alpha. IPv4/IPv6 UDP delivery probes, Linux no-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
+ ## Quick start
28
+
29
+ Requires Python 3.10 or newer. In an empty working directory on Linux (or
30
+ WSL2), install the published package in a virtual environment:
31
+
32
+ ```sh
33
+ python3 -m venv .venv
34
+ source .venv/bin/activate
35
+ python -m pip install tunnelscope
36
+ tunnelscope --help
37
+ ```
38
+
39
+ Open a second terminal and activate the same environment there with
40
+ `source .venv/bin/activate`. Run these commands in order on one machine; no
41
+ root access or network setup is needed for this loopback example.
42
+
43
+ Terminal 1 (receiver):
44
+
45
+ ```sh
46
+ tunnelscope receive --max-packets 2 --output receiver.json
47
+ ```
48
+
49
+ Wait for `Listening on 127.0.0.1:9471`, then run Terminal 2 (sender):
50
+
51
+ ```sh
52
+ tunnelscope probe 127.0.0.1 --sizes 64,1200 --count 1 --output probe.json
53
+ ```
54
+
55
+ The receiver exits after the two probes and saves `receiver.json`. Back in
56
+ either terminal, inspect the sender report and create a local HTML view:
57
+
58
+ ```sh
59
+ tunnelscope show probe.json
60
+ tunnelscope html probe.json --output probe.html
61
+ ```
62
+
63
+ Open `probe.html` in a browser. The receiver sends small acknowledgements, so
64
+ this example checks delivery in one direction only. A timeout is inconclusive;
65
+ it does not prove an MTU problem. For two machines, see the
66
+ [two-endpoint workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/bidirectional.md).
26
67
 
27
68
  ## The problem
28
69
 
@@ -49,7 +90,7 @@ A failed probe alone will not be labeled as proof of an MTU problem. Reports sep
49
90
  - [x] **M4 - Observe at the receiver:** record which identified probes reached the cooperating receiver and correlate them with sender outcomes.
50
91
  - [x] **M5 - Make evidence usable:** validate receiver-observed timeouts in a controlled lab and provide a concise terminal view of reports.
51
92
 
52
- Kubernetes, VPN-specific workflows, IPv6 and 5G user-plane tunnels are possible later directions. They are not currently supported features.
93
+ The [tunnel and pod workflows](https://github.com/lazypanic999/tunnelscope/blob/main/docs/environments.md) include a validated local VXLAN overlay and guidance for existing VPNs and Kubernetes pods. The VPN and pod workflows have not yet been validated on a real deployment. 5G user-plane tunnels remain outside the current scope.
53
94
 
54
95
  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
96
  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.
@@ -62,7 +103,16 @@ Use `tunnelscope show report.json` for a short terminal table of a probe,
62
103
  receiver observation, bidirectional comparison, correlation, or lab report.
63
104
  JSON remains the machine-readable source of evidence.
64
105
 
65
- ## Install locally
106
+ The CLI can also create a [self-contained HTML report view](https://github.com/lazypanic999/tunnelscope/blob/main/docs/report-ui.md)
107
+ that works offline:
108
+
109
+ ```sh
110
+ tunnelscope html report.json --output report.html
111
+ ```
112
+
113
+ ![TunnelScope HTML report view](https://raw.githubusercontent.com/lazypanic999/tunnelscope/main/docs/images/report-ui.png)
114
+
115
+ ## Install from a source checkout
66
116
 
67
117
  From the repository root, in a Python 3.10+ virtual environment:
68
118
 
@@ -71,29 +121,31 @@ python3 -m pip install .
71
121
  tunnelscope --help
72
122
  ```
73
123
 
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.
124
+ The package installs the `tunnelscope` command. It has no third-party runtime
125
+ dependencies. The laboratory scripts still run from a source checkout.
75
126
 
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):
127
+ The CLI also supports `--sweep START:STOP:STEP` for a bounded
128
+ grid of UDP payload sizes. It includes the stop size even when the step does
129
+ not land on it. For example:
81
130
 
82
131
  ```sh
83
- python tunnelscope.py receive
132
+ tunnelscope probe 192.0.2.2 --sweep 64:1400:100 --count 2 --dont-fragment --output sweep.json
84
133
  ```
85
134
 
86
- Terminal 2 (sender):
135
+ The grid tests at most 16 sizes. The result records observations at those
136
+ sizes; it is not an exact path-MTU measurement. See the [size sweep guide](https://github.com/lazypanic999/tunnelscope/blob/main/docs/size-sweep.md).
87
137
 
88
- ```sh
89
- python tunnelscope.py probe 127.0.0.1 --sizes 64,512,1200,1400 --count 3 --output report.json
90
- ```
138
+ ## Probe and report details
91
139
 
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.
140
+ No third-party runtime packages are needed. The commands below also work from
141
+ a source checkout by replacing `tunnelscope` with `python tunnelscope.py`.
142
+ By default, the receiver exits after 100 datagrams or 30 seconds without a
143
+ datagram. Restart it if it has already exited. `--max-packets` and
144
+ `--idle-timeout` change these limits.
93
145
 
94
146
  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
147
 
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.
148
+ Each probe report includes a `source` snapshot taken from the first connected sender socket. It records the local IPv4 or IPv6 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
149
 
98
150
  `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
151
 
@@ -104,18 +156,18 @@ The `evidence` section lists counted observations, tentative hypotheses and miss
104
156
  To compare directions, run `probe` once from A to B and once from B to A, then combine the two JSON files:
105
157
 
106
158
  ```sh
107
- python tunnelscope.py combine a-to-b.json b-to-a.json --output bidirectional.json
159
+ tunnelscope combine a-to-b.json b-to-a.json --output bidirectional.json
108
160
  ```
109
161
 
110
162
  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
163
 
112
- ### Linux IPv4 Don't Fragment mode
164
+ ### Linux no-fragment mode
113
165
 
114
166
  ```sh
115
- python tunnelscope.py probe 192.0.2.2 --sizes 64,1252,1253,1400 --count 1 --dont-fragment --output report.json
167
+ tunnelscope probe 192.0.2.2 --sizes 64,1252,1253,1400 --count 1 --dont-fragment --output report.json
116
168
  ```
117
169
 
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.
170
+ Use the address of your lab receiver; `192.0.2.2` is an example. For IPv4, 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. For IPv6, it sets Linux `IPV6_DONTFRAG` to prevent source fragmentation. 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. See the [IPv6 workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/ipv6.md).
119
171
 
120
172
  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
173
 
@@ -143,7 +195,7 @@ This lab creates a sender, router and receiver in temporary network namespaces.
143
195
 
144
196
  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
197
 
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.
198
+ The receiver binds to IPv4 loopback by default. For your own two-host lab, use `--bind <receiver-lab-address>` and probe that address. An IPv6 literal such as `::1` selects IPv6; `--family ipv6` selects an AAAA record for a hostname. The experimental protocol is unencrypted and unauthenticated; use a controlled lab, not a public service.
147
199
 
148
200
  Run checks:
149
201
 
@@ -6,7 +6,48 @@
6
6
 
7
7
  Evidence-based MTU and packet-size diagnostics for Linux networks.
8
8
 
9
- **Status: v0.2.2 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.**
9
+ **Status: v0.4.0 alpha. IPv4/IPv6 UDP delivery probes, Linux no-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
+ ## Quick start
12
+
13
+ Requires Python 3.10 or newer. In an empty working directory on Linux (or
14
+ WSL2), install the published package in a virtual environment:
15
+
16
+ ```sh
17
+ python3 -m venv .venv
18
+ source .venv/bin/activate
19
+ python -m pip install tunnelscope
20
+ tunnelscope --help
21
+ ```
22
+
23
+ Open a second terminal and activate the same environment there with
24
+ `source .venv/bin/activate`. Run these commands in order on one machine; no
25
+ root access or network setup is needed for this loopback example.
26
+
27
+ Terminal 1 (receiver):
28
+
29
+ ```sh
30
+ tunnelscope receive --max-packets 2 --output receiver.json
31
+ ```
32
+
33
+ Wait for `Listening on 127.0.0.1:9471`, then run Terminal 2 (sender):
34
+
35
+ ```sh
36
+ tunnelscope probe 127.0.0.1 --sizes 64,1200 --count 1 --output probe.json
37
+ ```
38
+
39
+ The receiver exits after the two probes and saves `receiver.json`. Back in
40
+ either terminal, inspect the sender report and create a local HTML view:
41
+
42
+ ```sh
43
+ tunnelscope show probe.json
44
+ tunnelscope html probe.json --output probe.html
45
+ ```
46
+
47
+ Open `probe.html` in a browser. The receiver sends small acknowledgements, so
48
+ this example checks delivery in one direction only. A timeout is inconclusive;
49
+ it does not prove an MTU problem. For two machines, see the
50
+ [two-endpoint workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/bidirectional.md).
10
51
 
11
52
  ## The problem
12
53
 
@@ -33,7 +74,7 @@ A failed probe alone will not be labeled as proof of an MTU problem. Reports sep
33
74
  - [x] **M4 - Observe at the receiver:** record which identified probes reached the cooperating receiver and correlate them with sender outcomes.
34
75
  - [x] **M5 - Make evidence usable:** validate receiver-observed timeouts in a controlled lab and provide a concise terminal view of reports.
35
76
 
36
- Kubernetes, VPN-specific workflows, IPv6 and 5G user-plane tunnels are possible later directions. They are not currently supported features.
77
+ The [tunnel and pod workflows](https://github.com/lazypanic999/tunnelscope/blob/main/docs/environments.md) include a validated local VXLAN overlay and guidance for existing VPNs and Kubernetes pods. The VPN and pod workflows have not yet been validated on a real deployment. 5G user-plane tunnels remain outside the current scope.
37
78
 
38
79
  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
80
  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.
@@ -46,7 +87,16 @@ Use `tunnelscope show report.json` for a short terminal table of a probe,
46
87
  receiver observation, bidirectional comparison, correlation, or lab report.
47
88
  JSON remains the machine-readable source of evidence.
48
89
 
49
- ## Install locally
90
+ The CLI can also create a [self-contained HTML report view](https://github.com/lazypanic999/tunnelscope/blob/main/docs/report-ui.md)
91
+ that works offline:
92
+
93
+ ```sh
94
+ tunnelscope html report.json --output report.html
95
+ ```
96
+
97
+ ![TunnelScope HTML report view](https://raw.githubusercontent.com/lazypanic999/tunnelscope/main/docs/images/report-ui.png)
98
+
99
+ ## Install from a source checkout
50
100
 
51
101
  From the repository root, in a Python 3.10+ virtual environment:
52
102
 
@@ -55,29 +105,31 @@ python3 -m pip install .
55
105
  tunnelscope --help
56
106
  ```
57
107
 
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.
108
+ The package installs the `tunnelscope` command. It has no third-party runtime
109
+ dependencies. The laboratory scripts still run from a source checkout.
59
110
 
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):
111
+ The CLI also supports `--sweep START:STOP:STEP` for a bounded
112
+ grid of UDP payload sizes. It includes the stop size even when the step does
113
+ not land on it. For example:
65
114
 
66
115
  ```sh
67
- python tunnelscope.py receive
116
+ tunnelscope probe 192.0.2.2 --sweep 64:1400:100 --count 2 --dont-fragment --output sweep.json
68
117
  ```
69
118
 
70
- Terminal 2 (sender):
119
+ The grid tests at most 16 sizes. The result records observations at those
120
+ sizes; it is not an exact path-MTU measurement. See the [size sweep guide](https://github.com/lazypanic999/tunnelscope/blob/main/docs/size-sweep.md).
71
121
 
72
- ```sh
73
- python tunnelscope.py probe 127.0.0.1 --sizes 64,512,1200,1400 --count 3 --output report.json
74
- ```
122
+ ## Probe and report details
75
123
 
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.
124
+ No third-party runtime packages are needed. The commands below also work from
125
+ a source checkout by replacing `tunnelscope` with `python tunnelscope.py`.
126
+ By default, the receiver exits after 100 datagrams or 30 seconds without a
127
+ datagram. Restart it if it has already exited. `--max-packets` and
128
+ `--idle-timeout` change these limits.
77
129
 
78
130
  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
131
 
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.
132
+ Each probe report includes a `source` snapshot taken from the first connected sender socket. It records the local IPv4 or IPv6 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
133
 
82
134
  `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
135
 
@@ -88,18 +140,18 @@ The `evidence` section lists counted observations, tentative hypotheses and miss
88
140
  To compare directions, run `probe` once from A to B and once from B to A, then combine the two JSON files:
89
141
 
90
142
  ```sh
91
- python tunnelscope.py combine a-to-b.json b-to-a.json --output bidirectional.json
143
+ tunnelscope combine a-to-b.json b-to-a.json --output bidirectional.json
92
144
  ```
93
145
 
94
146
  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
147
 
96
- ### Linux IPv4 Don't Fragment mode
148
+ ### Linux no-fragment mode
97
149
 
98
150
  ```sh
99
- python tunnelscope.py probe 192.0.2.2 --sizes 64,1252,1253,1400 --count 1 --dont-fragment --output report.json
151
+ tunnelscope probe 192.0.2.2 --sizes 64,1252,1253,1400 --count 1 --dont-fragment --output report.json
100
152
  ```
101
153
 
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.
154
+ Use the address of your lab receiver; `192.0.2.2` is an example. For IPv4, 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. For IPv6, it sets Linux `IPV6_DONTFRAG` to prevent source fragmentation. 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. See the [IPv6 workflow](https://github.com/lazypanic999/tunnelscope/blob/main/docs/ipv6.md).
103
155
 
104
156
  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
157
 
@@ -127,7 +179,7 @@ This lab creates a sender, router and receiver in temporary network namespaces.
127
179
 
128
180
  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
181
 
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.
182
+ The receiver binds to IPv4 loopback by default. For your own two-host lab, use `--bind <receiver-lab-address>` and probe that address. An IPv6 literal such as `::1` selects IPv6; `--family ipv6` selects an AAAA record for a hostname. The experimental protocol is unencrypted and unauthenticated; use a controlled lab, not a public service.
131
183
 
132
184
  Run checks:
133
185
 
@@ -0,0 +1,67 @@
1
+ # Controlled tunnel and pod workflows
2
+
3
+ TunnelScope sends ordinary UDP payloads between a cooperating sender and
4
+ receiver. The tool does not configure a VPN, container network, Kubernetes
5
+ cluster or network policy. Run it only where both endpoints and the UDP path
6
+ are under your control. Use the tunnel or pod address as the destination, and
7
+ measure each direction separately.
8
+
9
+ ## VXLAN overlay lab
10
+
11
+ On Linux with root and `iproute2`:
12
+
13
+ ```sh
14
+ sudo python3 labs/vxlan_boundary.py > vxlan-boundary.json
15
+ ```
16
+
17
+ The lab creates two temporary network namespaces. Their 1500-byte underlay
18
+ carries a VXLAN overlay with a 1450-byte interface MTU. In IPv4 DF mode, the
19
+ known maximum UDP payload on that overlay is 1422 bytes (1450 minus 20-byte
20
+ IPv4 and 8-byte UDP headers). The lab checks 1418–1426 bytes in both
21
+ directions, correlates receiver observations and removes its namespaces on
22
+ ordinary completion. It changes no host interface or route.
23
+
24
+ This validates an encapsulated virtual path, not a real VPN or a Kubernetes
25
+ cluster. A measured local tunnel-interface MTU and a kernel route MTU remain
26
+ different from independent proof of an unknown end-to-end path MTU.
27
+
28
+ ## Existing VPN or tunnel
29
+
30
+ Use the addresses assigned inside the tunnel. Start the receiver on endpoint
31
+ B, then probe from endpoint A:
32
+
33
+ ```sh
34
+ tunnelscope receive --bind B_TUNNEL_IP --output b-receiver.json
35
+ tunnelscope probe B_TUNNEL_IP --sweep 1200:1400:25 --count 2 --dont-fragment --output a-to-b.json
36
+ ```
37
+
38
+ Run the commands in separate terminals. Repeat with A and B swapped, then use
39
+ `combine` for direction comparison and `correlate` with each receiver report.
40
+ Check the selected `source.interface` and its MTU in the probe report. If the
41
+ route unexpectedly uses a non-tunnel interface, the experiment did not measure
42
+ the intended VPN path. Use `--family ipv6` for an IPv6 hostname or an IPv6
43
+ literal address. Timeouts remain inconclusive without receiver or path
44
+ evidence.
45
+
46
+ ## Existing Kubernetes pods
47
+
48
+ If two controlled pods already contain TunnelScope v0.3.0 and Python 3.10+,
49
+ use their pod addresses directly. In separate local terminals, for example:
50
+
51
+ ```sh
52
+ kubectl exec -n NAMESPACE POD_B -- tunnelscope receive --bind POD_B_IP --output /tmp/receiver.json
53
+ kubectl exec -n NAMESPACE POD_A -- tunnelscope probe POD_B_IP --sizes 64,1200,1400 --count 2 --dont-fragment --output /tmp/probe.json
54
+ ```
55
+
56
+ Collect the JSON files from the pods and correlate them. Repeat with the roles
57
+ reversed. A pod may have more than one container; add `-c CONTAINER` to
58
+ `kubectl exec` when needed. Ensure that the intended UDP port (default 9471)
59
+ and the small ACK return traffic are allowed by the cluster's network policy.
60
+ Kubernetes [pod networking](https://kubernetes.io/docs/concepts/services-networking/)
61
+ and [NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
62
+ are implemented by the cluster's network plugin; behavior can differ between
63
+ clusters. See the official [`kubectl exec` reference](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_exec/).
64
+
65
+ This pod recipe is untested here because no Kubernetes cluster was available.
66
+ It does not claim cluster-wide MTU discovery, support for every CNI, or the
67
+ ability to distinguish overlay loss from filtering on its own.
@@ -0,0 +1,37 @@
1
+ # IPv6 delivery experiments
2
+
3
+ From v0.3.0, the CLI supports IPv6 UDP payload probes and receiver observations.
4
+ Use an IPv6 literal for `probe` or `receive --bind`; the default receiver still
5
+ binds to IPv4 loopback. For a hostname, `--family ipv6` selects an AAAA record.
6
+ The default hostname behavior continues to prefer IPv4 for compatibility.
7
+
8
+ On one machine, start the receiver in one terminal:
9
+
10
+ ```sh
11
+ tunnelscope receive --bind ::1 --output receiver-v6.json
12
+ ```
13
+
14
+ In another terminal:
15
+
16
+ ```sh
17
+ tunnelscope probe ::1 --sizes 64,1200,1400 --count 2 --dont-fragment --output probe-v6.json
18
+ ```
19
+
20
+ For a two-host test, use the receiver's reachable IPv6 address and repeat the
21
+ procedure with endpoint roles reversed. Link-local addresses need a zone
22
+ identifier such as `%eth0` on the sender. Reports use `source.ipv6`,
23
+ `target.ipv6`, `receiver.bind_ipv6` and `source_ipv6` in receiver observations.
24
+ The existing report schema version remains 1 with additive IPv6 fields.
25
+
26
+ On Linux, `--dont-fragment` sets `IPV6_DONTFRAG` on the sender socket. An
27
+ `EMSGSIZE` error is recorded as `packet_too_large`; a reported `IPV6_MTU`
28
+ value is the kernel's current route knowledge, not a separately measured
29
+ end-to-end path MTU. Without the flag, source fragmentation follows OS
30
+ defaults. Timeouts remain inconclusive. The receiver's acknowledgement is
31
+ small, so reverse roles to test the opposite direction with the same payload
32
+ size.
33
+
34
+ The [isolated IPv6 boundary lab](../labs/ipv6_boundary.py) verifies a
35
+ 1280-byte veth link: 1228–1232-byte UDP payloads arrive, while 1233–1236-byte
36
+ payloads are rejected locally in no-fragment mode. It creates and removes
37
+ temporary network namespaces and requires root and `iproute2`.
@@ -0,0 +1,25 @@
1
+ # TunnelScope v0.3.0
2
+
3
+ Alpha release of the Python CLI with more controlled network experiments and
4
+ a local report view.
5
+
6
+ ## Highlights
7
+
8
+ - IPv6 UDP probes, cooperating receiver reports, run correlation and
9
+ bidirectional comparisons. Linux `--dont-fragment` uses `IPV6_DONTFRAG` for
10
+ IPv6 and retains IPv4 DF behavior.
11
+ - Bounded `--sweep START:STOP:STEP` payload-size grids (at most 16 sizes).
12
+ - Reproducible network namespace labs for a known IPv6 link boundary and a
13
+ VXLAN overlay boundary, both checked in each direction.
14
+ - `tunnelscope html report.json --output report.html` creates a searchable
15
+ local report view. The report content remains in the generated HTML.
16
+ - VPN and Kubernetes pod workflow guidance. Those two deployment types are
17
+ documented but have not been validated on a real deployment.
18
+
19
+ ## Known limits
20
+
21
+ The tool does not discover an exact end-to-end path MTU or prove a root cause
22
+ on an unknown network. A timeout remains inconclusive without additional
23
+ receiver or path evidence. The UDP protocol is unauthenticated and unencrypted;
24
+ use it only in a controlled environment. The HTML view fetches the project
25
+ logo from GitHub when online, but does not send the report to a service.
@@ -0,0 +1,22 @@
1
+ # TunnelScope v0.4.0
2
+
3
+ Alpha release focused on easier first use and portable reports.
4
+
5
+ ## Highlights
6
+
7
+ - Generated HTML reports embed the project logo and work without a network
8
+ connection or extra files. Search now shows the number of matching rows and
9
+ a message when nothing matches.
10
+ - The README has a short path from installation to a local probe and HTML
11
+ report.
12
+ - `tunnelscope --version` shows the installed CLI version.
13
+ - `html`, `combine` and `correlate` reject an output path that points to an
14
+ input report, preventing accidental overwrite.
15
+
16
+ ## Known limits
17
+
18
+ TunnelScope does not discover an exact end-to-end path MTU or prove a root
19
+ cause on an unknown network. A timeout remains inconclusive without more
20
+ receiver or path evidence. Its UDP protocol is unauthenticated and
21
+ unencrypted; use it only in a controlled environment. Real VPN and Kubernetes
22
+ workflows remain documented but unvalidated on a deployment.
@@ -0,0 +1,22 @@
1
+ # Local HTML report view
2
+
3
+ The CLI can turn an existing JSON report into a searchable HTML file. From
4
+ v0.4.0, the logo is embedded too, so the file works fully offline. It does not
5
+ start a server or send the report to a remote service.
6
+
7
+ ```sh
8
+ tunnelscope html probe.json --output probe.html
9
+ ```
10
+
11
+ Open `probe.html` in a browser. Search filters visible rows; the raw JSON is
12
+ available in a collapsed section. The view supports probe, receiver,
13
+ correlation, bidirectional comparison and lab reports. It presents evidence
14
+ already in the JSON; it does not run a new measurement or infer a root cause.
15
+ The logo is embedded in the generated HTML. The HTML is a self-contained file:
16
+ the logo, report content, styles and search work without network access.
17
+
18
+ ![TunnelScope HTML report view](images/report-ui.png)
19
+
20
+ The generated HTML contains the complete report, including addresses and any
21
+ other fields in its JSON. Sanitize the JSON before sharing the HTML. Report
22
+ values are HTML-escaped before rendering.
@@ -0,0 +1,29 @@
1
+ # Bounded payload-size sweep
2
+
3
+ From v0.3.0, `probe --sweep START:STOP:STEP` expands
4
+ an ascending grid of UDP payload sizes, always including `STOP` even if `STEP`
5
+ does not land exactly on it. The bounds are 32 to 8192 bytes and at most 16
6
+ sizes. `--sweep` and `--sizes` cannot be used together. `--count` repeats each
7
+ size; existing per-size status counts and individual results remain in JSON.
8
+
9
+ On a controlled Linux IPv4 link, start the receiver first and run:
10
+
11
+ ```sh
12
+ tunnelscope receive --bind RECEIVER_IP --output receiver.json
13
+ tunnelscope probe RECEIVER_IP --sweep 1200:1400:25 --count 2 --dont-fragment --output sweep.json
14
+ tunnelscope show sweep.json
15
+ ```
16
+
17
+ Run the receiver and sender commands on separate terminals or hosts. The
18
+ receiver's small acknowledgement does not test a same-sized return packet.
19
+ Repeat with the endpoint roles reversed to test the other direction.
20
+
21
+ A sweep samples only the listed sizes. A successful size is an observed working
22
+ UDP payload size under these conditions, not the exact path MTU. A
23
+ `packet_too_large` result reflects the sender kernel's DF rejection and
24
+ current route knowledge. A timeout is inconclusive even if nearby smaller
25
+ sizes succeeded. Compare receiver observations with `correlate` to learn
26
+ whether a timed-out probe reached the cooperating receiver.
27
+
28
+ The [direct-link MTU lab](../labs/mtu_boundary.py) checks this option against a
29
+ known 1280-byte virtual link in both directions.
@@ -0,0 +1,44 @@
1
+ # Two-VM payload-size sweep validation
2
+
3
+ Recorded on 2026-10-28 using separate Ubuntu 24.04 and Debian 12 VirtualBox
4
+ guests connected through a temporary 1500-byte host-only link. Ubuntu ran
5
+ Python 3.12.3 and Debian ran Python 3.11.2. Both guests ran the same
6
+ `tunnelscope.py` file (SHA-256
7
+ `43b0cb76dfa6b58452b9ecc844a5db27c2d057cb7cad4c3735233f75d454a857`).
8
+ The temporary host-only addresses and raw JSON reports are kept locally in
9
+ ignored `output/field-test-2026-10-28/`; they are not committed.
10
+
11
+ ## Procedure
12
+
13
+ The destination ran `receive --max-packets 5 --idle-timeout 20 --output receiver.json`.
14
+ The source then ran the development CLI in Linux IPv4 DF mode:
15
+
16
+ ```sh
17
+ python3 tunnelscope.py probe PEER_IP --sweep 1468:1476:1 --count 1 --timeout 2 --dont-fragment --output probe.json
18
+ ```
19
+
20
+ The two files were correlated after each direction. The same procedure was
21
+ repeated with guest roles reversed. The direct-link expected UDP payload
22
+ boundary is 1472 bytes: 1500-byte link MTU minus 20-byte IPv4 and 8-byte UDP
23
+ headers.
24
+
25
+ ## Results
26
+
27
+ | Direction | 1468–1472 bytes | 1473–1476 bytes | Receiver observed | Run ID |
28
+ | --- | --- | --- | ---: | --- |
29
+ | Ubuntu to Debian | 5/5 acknowledged | 4/4 `packet_too_large` | 5/5 | matched |
30
+ | Debian to Ubuntu | 5/5 acknowledged | 4/4 `packet_too_large` | 5/5 | matched |
31
+
32
+ Each sender identified its host-only interface with MTU 1500. All eight
33
+ oversized attempts reported kernel MTU 1500. Both receiver reports exited at
34
+ `max_packets`; the correlations had zero unmatched observations and no
35
+ receiver-observed timeouts. The source file was checked by SHA-256 on both
36
+ guests before testing.
37
+
38
+ ## Limits
39
+
40
+ This confirms the size grid and known boundary on one directly connected
41
+ virtual link. It does not discover an unknown multi-hop path MTU. The
42
+ `packet_too_large` statuses are local kernel rejections, and the successful
43
+ ACKs only confirm forward payload delivery plus a small reply. The same
44
+ experiment on an unknown path can produce timeouts for several reasons.