PyTCP 2.7.9__tar.gz → 3.0.4__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.
- pytcp-3.0.4/PKG-INFO +685 -0
- pytcp-3.0.4/PyTCP.egg-info/PKG-INFO +685 -0
- pytcp-3.0.4/PyTCP.egg-info/SOURCES.txt +34 -0
- pytcp-3.0.4/PyTCP.egg-info/top_level.txt +3 -0
- pytcp-3.0.4/README.md +664 -0
- pytcp-3.0.4/net_addr/__init__.py +136 -0
- pytcp-3.0.4/net_addr/address.py +93 -0
- pytcp-3.0.4/net_addr/base.py +71 -0
- pytcp-3.0.4/net_addr/click_types.py +358 -0
- pytcp-3.0.4/net_addr/errors.py +190 -0
- pytcp-3.0.4/net_addr/ip.py +69 -0
- pytcp-3.0.4/net_addr/ip4_address.py +267 -0
- pytcp-3.0.4/net_addr/ip4_host.py +134 -0
- pytcp-3.0.4/net_addr/ip4_host_origin.py +45 -0
- pytcp-3.0.4/net_addr/ip4_mask.py +103 -0
- pytcp-3.0.4/net_addr/ip4_network.py +108 -0
- pytcp-3.0.4/net_addr/ip6_address.py +288 -0
- pytcp-3.0.4/net_addr/ip6_host.py +284 -0
- pytcp-3.0.4/net_addr/ip6_host_origin.py +46 -0
- pytcp-3.0.4/net_addr/ip6_mask.py +94 -0
- pytcp-3.0.4/net_addr/ip6_network.py +98 -0
- pytcp-3.0.4/net_addr/ip_address.py +107 -0
- pytcp-3.0.4/net_addr/ip_host.py +167 -0
- pytcp-3.0.4/net_addr/ip_host_origin.py +39 -0
- pytcp-3.0.4/net_addr/ip_mask.py +101 -0
- pytcp-3.0.4/net_addr/ip_network.py +122 -0
- pytcp-3.0.4/net_addr/ip_version.py +49 -0
- pytcp-3.0.4/net_addr/mac_address.py +159 -0
- pytcp-3.0.4/net_proto/__init__.py +828 -0
- pytcp-3.0.4/pyproject.toml +109 -0
- pytcp-3.0.4/pytcp/__init__.py +38 -0
- pytcp-3.0.4/pytcp/template.py +31 -0
- pytcp-2.7.9/PKG-INFO +0 -355
- pytcp-2.7.9/PyTCP.egg-info/PKG-INFO +0 -355
- pytcp-2.7.9/PyTCP.egg-info/SOURCES.txt +0 -10
- pytcp-2.7.9/PyTCP.egg-info/top_level.txt +0 -1
- pytcp-2.7.9/README.md +0 -334
- pytcp-2.7.9/pyproject.toml +0 -73
- pytcp-2.7.9/pytcp/__init__.py +0 -150
- pytcp-2.7.9/pytcp/config.py +0 -146
- {pytcp-2.7.9 → pytcp-3.0.4}/LICENSE +0 -0
- {pytcp-2.7.9 → pytcp-3.0.4}/PyTCP.egg-info/dependency_links.txt +0 -0
- {pytcp-2.7.9 → pytcp-3.0.4}/pytcp/py.typed +0 -0
- {pytcp-2.7.9 → pytcp-3.0.4}/setup.cfg +0 -0
pytcp-3.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyTCP
|
|
3
|
+
Version: 3.0.4
|
|
4
|
+
Summary: Pure-Python, zero-dependency TCP/IP stack — Ethernet through RFC 9293 TCP — running in user space on a TAP/TUN interface, with a Berkeley-sockets API.
|
|
5
|
+
Author-email: Sebastian Majewski <ccie18643@gmail.com>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/ccie18643/PyTCP
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/ccie18643/PyTCP/issues
|
|
9
|
+
Keywords: pytcp,stack,networking,tcp,ip,ipv4,ipv6,arp,ethernet,icmp
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Topic :: System :: Networking
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# PyTCP
|
|
23
|
+
**The TCP/IP stack written in Python**
|
|
24
|
+
<br>
|
|
25
|
+
|
|
26
|
+
[](https://github.com/ccie18643/PyTCP/releases)
|
|
27
|
+
[](https://kernel.org)
|
|
28
|
+
[](https://pypi.org/project/PyTCP)
|
|
29
|
+
[](https://github.com/ccie18643/PyTCP/blob/master/LICENSE)
|
|
30
|
+
[](https://github.com/ccie18643/PyTCP/actions/workflows/ci.yml)
|
|
31
|
+
|
|
32
|
+
[](https://GitHub.com/ccie18643/PyTCP/watchers/)
|
|
33
|
+
[](https://GitHub.com/ccie18643/PyTCP/network/)
|
|
34
|
+
[](https://GitHub.com/ccie18643/PyTCP/stargazers/)
|
|
35
|
+
|
|
36
|
+
<br>
|
|
37
|
+
|
|
38
|
+
**PyTCP is a TCP/IP stack written in pure Python.** It runs in user space, attached to a Linux TAP/TUN interface, and implements the protocol layers itself rather than calling the host stack.
|
|
39
|
+
|
|
40
|
+
The stack covers Ethernet II and IEEE 802.3 framing, ARP, IPv4 and IPv6 (extension headers and fragmentation), ICMPv4 and ICMPv6, IPv6 Neighbor Discovery and SLAAC, a DHCPv4 client, UDP, and RFC 9293 TCP. The TCP implementation includes the full finite state machine, congestion control (CUBIC, NewReno, PRR, HyStart++), SACK and RACK-TLP loss recovery, and RFC 5961 hardening. It exchanges traffic with other hosts on the local segment and over the Internet.
|
|
41
|
+
|
|
42
|
+
The project's goal is a pure-Python stack that is feature-equivalent to the Linux kernel network stack. RFC text is the primary authority; where a spec is silent or offers a choice, PyTCP follows Linux. Host-stack parity is the current scope; router-grade forwarding is planned.
|
|
43
|
+
|
|
44
|
+
Behaviour is covered by roughly 11,000 unit and integration tests and tracked against more than 100 per-RFC adherence audits kept in the repository under `docs/rfc/`.
|
|
45
|
+
|
|
46
|
+
The stack has zero runtime dependencies (standard library only), is organised as three packages (`net_addr`, `net_proto`, `pytcp`), and exposes a Berkeley-sockets-style API so it can be used in place of the standard socket layer.
|
|
47
|
+
|
|
48
|
+
Contributions are welcome.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Features
|
|
54
|
+
|
|
55
|
+
#### Stack & sockets (engineering, non-RFC)
|
|
56
|
+
|
|
57
|
+
- Zero-copy packet parser and assembler (buffer-protocol / memoryview based).
|
|
58
|
+
- `net_addr` value-type libraries for MAC / IPv4 / IPv6 addresses, networks, hosts and masks - no Python standard-library dependency.
|
|
59
|
+
- Importable as a zero-runtime-dependency library (stdlib only), split into three independent packages: `net_addr`, `net_proto`, `pytcp`.
|
|
60
|
+
- Event-driven millisecond-resolution timer (heap-based deadline scheduler, no polling tick).
|
|
61
|
+
- Runtime-tunable sysctl registry mirroring the Linux `/proc/sys/net/` surface (boot-time and live overrides).
|
|
62
|
+
- Link control API (ip-link-style): per-interface MAC / MTU / state / counters.
|
|
63
|
+
- Per-protocol packet-flow stat counters; TX-path feedback so send failures reach sockets.
|
|
64
|
+
- Homegrown high-performance logger (no third-party logging dependency).
|
|
65
|
+
- Berkeley-sockets-style API for TCP / UDP / RAW: `fileno()`/eventfd + `selectors` integration, blocking & non-blocking modes, errno-mapped `OSError`, `getaddrinfo` family, common `setsockopt` options, `IP_RECVERR`/`MSG_ERRQUEUE` error queue.
|
|
66
|
+
- Native `unittest` suite (~11,000 unit + integration tests); per-RFC adherence audits in `docs/rfc/`.
|
|
67
|
+
|
|
68
|
+
#### Ethernet
|
|
69
|
+
|
|
70
|
+
- Ethernet II framing with EtherType demux, broadcast and multicast mapping (RFC 894)
|
|
71
|
+
- Inbound IEEE 802.3 / LLC + SNAP support (RFC 1042)
|
|
72
|
+
|
|
73
|
+
#### ARP
|
|
74
|
+
|
|
75
|
+
- ARP resolution with a neighbor cache, replies and queries (RFC 826, RFC 1122)
|
|
76
|
+
- IPv4 Address Conflict Detection — probe, announce, defend (RFC 5227)
|
|
77
|
+
- IANA-correct ARP codepoint handling (RFC 5494)
|
|
78
|
+
|
|
79
|
+
#### IPv4
|
|
80
|
+
|
|
81
|
+
- IPv4 with options parsing, inbound reassembly and outbound fragmentation (RFC 791, RFC 815)
|
|
82
|
+
- Multiple host addresses; private, special-purpose and broadcast address handling (RFC 1918, RFC 6890, RFC 919, RFC 922)
|
|
83
|
+
- ECN, DSCP and Router Alert support (RFC 3168, RFC 2474, RFC 6398)
|
|
84
|
+
- IPv4 link-local autoconfiguration (RFC 3927)
|
|
85
|
+
- Host-side IP multicasting (RFC 1112)
|
|
86
|
+
|
|
87
|
+
#### ICMPv4
|
|
88
|
+
|
|
89
|
+
- Echo, Destination Unreachable, Time Exceeded and Parameter Problem, with RFC-correct generation gating and rate-limiting (RFC 792, RFC 1122)
|
|
90
|
+
- Obsolete message types correctly omitted (RFC 6633, RFC 6918)
|
|
91
|
+
|
|
92
|
+
#### IPv6
|
|
93
|
+
|
|
94
|
+
- IPv6 with the full extension-header chain and TLV options (RFC 8200)
|
|
95
|
+
- Inbound reassembly and outbound fragmentation, with fragmentation hardening (RFC 5722, RFC 6946, RFC 7739)
|
|
96
|
+
- Unique-local and special-purpose addressing (RFC 4193, RFC 8190)
|
|
97
|
+
- Flow-label generation (RFC 6437)
|
|
98
|
+
- Default source-address selection (RFC 6724); Path MTU Discovery (RFC 8201); node requirements (RFC 8504)
|
|
99
|
+
|
|
100
|
+
#### ICMPv6 / Neighbor Discovery
|
|
101
|
+
|
|
102
|
+
- Full ICMPv6 message set including Packet Too Big (RFC 4443)
|
|
103
|
+
- Stateless Address Autoconfiguration: link-local, DAD, RA prefixes and lifetimes (RFC 4862)
|
|
104
|
+
- Stable opaque and temporary (privacy) addresses (RFC 7217, RFC 8981)
|
|
105
|
+
- Optimistic DAD, Enhanced DAD and Gratuitous NA (RFC 4429, RFC 7527, RFC 9131)
|
|
106
|
+
- Neighbor Discovery with a NUD cache and Router Solicitation backoff (RFC 4861, RFC 7559)
|
|
107
|
+
- MLDv2 listener (RFC 3810)
|
|
108
|
+
|
|
109
|
+
#### UDP
|
|
110
|
+
|
|
111
|
+
- UDP with full host-requirements conformance (RFC 768, RFC 1122)
|
|
112
|
+
- Zero-checksum UDP over IPv6 (RFC 6935)
|
|
113
|
+
- Ephemeral-port randomisation (RFC 6056)
|
|
114
|
+
- Echo / Discard / Daytime example services
|
|
115
|
+
|
|
116
|
+
#### TCP
|
|
117
|
+
|
|
118
|
+
- Complete TCP: full finite state machine and reliable bulk transfer (RFC 9293, RFC 1122)
|
|
119
|
+
- Modern congestion control — CUBIC, NewReno, PRR, HyStart++, ABE, IW10 (RFC 9438, RFC 6582, RFC 6937, RFC 9406, RFC 8511, RFC 6928)
|
|
120
|
+
- Advanced loss recovery — SACK, D-SACK, RACK-TLP, F-RTO, limited transmit (RFC 2018, RFC 2883, RFC 8985, RFC 5682, RFC 3042)
|
|
121
|
+
- RFC-correct RTO with Karn's algorithm and backoff (RFC 6298, RFC 8961)
|
|
122
|
+
- Window Scale, Timestamps, PAWS, MSS and TCP Fast Open (RFC 7323, RFC 6691, RFC 7413)
|
|
123
|
+
- ECN and Accurate ECN (RFC 3168, RFC 9768)
|
|
124
|
+
- Blind-attack and ICMP-attack hardening, randomised ISS and ports, robust TIME-WAIT (RFC 5961, RFC 5927, RFC 6528, RFC 1337, RFC 6191)
|
|
125
|
+
- Keep-alive, zero-window probing, silly-window-syndrome avoidance, Nagle
|
|
126
|
+
|
|
127
|
+
#### DHCPv4 client
|
|
128
|
+
|
|
129
|
+
- Full DHCPv4 client: lease acquisition, RENEW / REBIND / DECLINE (RFC 2131, RFC 1542)
|
|
130
|
+
- Detecting Network Attachment and client-ID handling (RFC 4436, RFC 6842, RFC 4361)
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
### Principle of operation and the test setup
|
|
136
|
+
|
|
137
|
+
The PyTCP stack depends on a Linux TAP/TUN interface. The TAP interface is a virtual interface that,
|
|
138
|
+
on the network end, can be 'plugged' into existing virtual network infrastructure via either Linux
|
|
139
|
+
bridge or Open vSwitch. On the internal end, the TAP interface can be used like any other NIC by
|
|
140
|
+
programmatically sending and receiving packets to/from it.
|
|
141
|
+
|
|
142
|
+
If you wish to test the PyTCP stack in your local network, I'd suggest creating the following network
|
|
143
|
+
setup that will allow you to connect both the Linux kernel (essentially your Linux OS) and the
|
|
144
|
+
PyTCP stack to your local network at the same time.
|
|
145
|
+
|
|
146
|
+
```console
|
|
147
|
+
<INTERNET> <---> [ROUTER] <---> (eth0)-[Linux bridge]-(br0) <---> [Linux TCP/IP stack]
|
|
148
|
+
|
|
|
149
|
+
|--(tap7) <---> [PyTCP TCP/IP stack]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
After the example program (either client or service) starts the stack, it can communicate with it
|
|
153
|
+
via simplified BSD Sockets like API interface. There is also the possibility of sending packets
|
|
154
|
+
directly by calling one of the internal ```_phtx_*()``` methods on the ```PacketHandler```.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
### Cloning PyTCP from the GitHub repository
|
|
160
|
+
|
|
161
|
+
In most cases, PyTCP should be cloned directly from the [GitHub repository](https://github.com/ccie18643/PyTCP),
|
|
162
|
+
as this type of installation provides full development and testing environment.
|
|
163
|
+
|
|
164
|
+
```shell
|
|
165
|
+
git clone https://github.com/ccie18643/PyTCP
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
After cloning, we can run one of the included examples:
|
|
169
|
+
- Go to the stack root directory (it is called 'PyTCP').
|
|
170
|
+
- Run the ```sudo make bridge``` command to create the 'br0' bridge if needed.
|
|
171
|
+
- Run the ```sudo make tap7``` command to create the tap7 interface and assign it to the 'br0' bridge.
|
|
172
|
+
- Run the ```make venv``` command to create the virtual environment for development and testing.
|
|
173
|
+
- Run ```. venv/bin/activate``` command to activate the virtual environment.
|
|
174
|
+
- Execute any example, e.g., ```python -m examples.stack``` (see the ```examples/``` directory; pass ```--help``` for options).
|
|
175
|
+
- Hit Ctrl-C to stop it.
|
|
176
|
+
|
|
177
|
+
Stack parameters are configured per run via the ```stack.init(...)``` keyword arguments and the runtime sysctl registry (see ```pytcp/stack/```), not a static config file.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
### Installing PyTCP from the PyPi repository
|
|
183
|
+
|
|
184
|
+
PyTCP can also be installed as a regular module from the [PyPi repository](https://pypi.org/project/PyTCP/).
|
|
185
|
+
|
|
186
|
+
```console
|
|
187
|
+
python -m pip install PyTCP
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
After installation, please ensure the TAP interface is operational and added to the bridge.
|
|
191
|
+
|
|
192
|
+
```console
|
|
193
|
+
sudo ip tuntap add name tap7 mode tap
|
|
194
|
+
sudo ip link set dev tap7 up
|
|
195
|
+
sudo ip link add name br0 type bridge
|
|
196
|
+
sudo ip link set dev br0 up
|
|
197
|
+
sudo ip link set dev tap7 master br0
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
PyTCP is consumed as a library through the ```pytcp.stack``` lifecycle API
|
|
201
|
+
(```stack.init(...)``` → ```stack.start()``` → ```stack.stop()```) and the
|
|
202
|
+
```pytcp.socket``` Berkeley-sockets-style API. The subsystems run in their own
|
|
203
|
+
threads; after ```start()``` control returns to your code.
|
|
204
|
+
|
|
205
|
+
For a complete, runnable reference — opening the TAP/TUN file descriptor,
|
|
206
|
+
calling ```stack.init(...)```, and driving the stack — see
|
|
207
|
+
[```examples/stack.py```](examples/stack.py) and the other programs in the
|
|
208
|
+
[```examples/```](examples/) directory.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
### Examples
|
|
214
|
+
|
|
215
|
+
All output below is captured from a live stack on a Linux `tap7`
|
|
216
|
+
interface bridged to a LAN — PyTCP's own log plus a `tshark` wire
|
|
217
|
+
capture. RFC back-off delays (RFC 5227 ACD, RFC 4862 DAD) are
|
|
218
|
+
visible in the timestamps.
|
|
219
|
+
|
|
220
|
+
Every wire block uses the same columns:
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
time(s) PROTO src → dst summary
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`src → dst` is the IPv4/IPv6 source → destination; for ARP it is
|
|
227
|
+
the ARP-payload **sender → target**. `—` marks IPv6 ND/MLD frames
|
|
228
|
+
whose link-local/multicast endpoints are named in the summary
|
|
229
|
+
instead (the `boot` capture did not record them as columns).
|
|
230
|
+
|
|
231
|
+
Every example is produced by the bundled `tools/capture` runner
|
|
232
|
+
and is reproducible. With the TAP/bridge up and the venv built —
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
sudo make tap7 && sudo make bridge && make venv
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
— run any example with the exact command listed under it (loss is
|
|
239
|
+
random, so a `--loss` run differs every time; everything else is
|
|
240
|
+
deterministic). The general form is
|
|
241
|
+
`sudo PYTHONPATH=. venv/bin/python -m tools.capture [GLOBAL OPTS] <scenario>`;
|
|
242
|
+
`python -m tools.capture --help` lists every scenario and option.
|
|
243
|
+
|
|
244
|
+
#### Stack startup — IPv6 SLAAC + DAD, MLDv2, IPv4 ACD
|
|
245
|
+
|
|
246
|
+
**Reproduce:**
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture boot
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
On start the stack autoconfigures itself: it derives an IPv6
|
|
253
|
+
link-local address and runs Duplicate Address Detection, reports its
|
|
254
|
+
multicast groups via MLDv2, solicits routers, builds a global
|
|
255
|
+
address from the Router Advertisement and DADs that too, then runs
|
|
256
|
+
RFC 5227 conflict detection for its IPv4 address.
|
|
257
|
+
|
|
258
|
+
Stack log:
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
0000.05 | STACK | ICMPv6 ND DAD - Starting process for fe80::7bde:94e9:3254:9daf
|
|
262
|
+
0001.28 | STACK | ICMPv6 ND DAD - No duplicate address detected for fe80::7bde:94e9:3254:9daf
|
|
263
|
+
0001.28 | STACK | Successfully claimed IPv6 address fe80::7bde:94e9:3254:9daf/64
|
|
264
|
+
0001.28 | STACK | Sent out ICMPv6 ND Router Solicitation
|
|
265
|
+
0001.28 | STACK | ICMPv6 ND DAD - Starting process for 2603:808c:2800:4301:7d08:ba99:95db:c5
|
|
266
|
+
0002.78 | STACK | Successfully claimed IPv6 address 2603:808c:2800:4301:7d08:ba99:95db:c5/64
|
|
267
|
+
0006.21 | STACK | Sent out ARP Announcement for 192.168.1.77
|
|
268
|
+
0008.21 | STACK | Successfully claimed IPv4 address 192.168.1.77
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Wire capture (`tshark -i tap7`, rebased to the first frame; IPv6
|
|
272
|
+
ND/MLD endpoints are now real columns, not `—`):
|
|
273
|
+
|
|
274
|
+
```text
|
|
275
|
+
0.000 ARP 0.0.0.0 → 192.168.1.77 Who has 192.168.1.77? (ARP Probe)
|
|
276
|
+
0.177 ICMPv6 :: → ff02::1:ff54:9daf Neighbor Solicitation for fe80::7bde:94e9:3254:9daf (link-local DAD)
|
|
277
|
+
1.178 ICMPv6 fe80::7bde:94e9:3254:9daf → ff02::16 Multicast Listener Report Message v2
|
|
278
|
+
1.179 ICMPv6 fe80::7bde:94e9:3254:9daf → ff02::2 Router Solicitation from 02:00:00:77:77:77
|
|
279
|
+
1.679 ICMPv6 :: → ff02::1:ffdb:c5 Neighbor Solicitation for 2603:808c:2800:4301:7d08:ba99:95db:c5 (SLAAC GUA DAD)
|
|
280
|
+
6.107 ARP 192.168.1.77 → 192.168.1.77 ARP Announcement for 192.168.1.77
|
|
281
|
+
6.180 ICMPv6 fe80::7bde:94e9:3254:9daf → fe80::2e0:67ff:fe26:88cb Neighbor Advertisement fe80::7bde:94e9:3254:9daf (sol) is at 02:00:00:77:77:77
|
|
282
|
+
8.108 ARP 192.168.1.77 → 192.168.1.77 ARP Announcement for 192.168.1.77
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### ARP Probe / Announcement (RFC 5227 Address Conflict Detection)
|
|
286
|
+
|
|
287
|
+
**Reproduce:**
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture arp-acd
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The stack defends each configured IPv4 address: it sends three ARP
|
|
294
|
+
**Probes** (sender `0.0.0.0`), and if no host objects, claims the
|
|
295
|
+
address with two ARP **Announcements** (sender = target).
|
|
296
|
+
|
|
297
|
+
Wire capture (`tshark -i tap7 -f arp`):
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
0.00 ARP 0.0.0.0 → 192.168.1.77 ARP Probe — Who has 192.168.1.77?
|
|
301
|
+
1.83 ARP 0.0.0.0 → 192.168.1.77 ARP Probe — Who has 192.168.1.77?
|
|
302
|
+
3.38 ARP 0.0.0.0 → 192.168.1.77 ARP Probe — Who has 192.168.1.77?
|
|
303
|
+
6.44 ARP 192.168.1.77 → 192.168.1.77 ARP Announcement for 192.168.1.77
|
|
304
|
+
8.45 ARP 192.168.1.77 → 192.168.1.77 ARP Announcement for 192.168.1.77
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Probe vs. Announcement, decoded (`tshark -V`):
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
ARP Probe Opcode: request Sender IP: 0.0.0.0 Target IP: 192.168.1.77
|
|
311
|
+
ARP Announcement Opcode: request Sender IP: 192.168.1.77 Target IP: 192.168.1.77
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### ARP resolution and ICMP Echo
|
|
315
|
+
|
|
316
|
+
**Reproduce:**
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip4-icmp-echo
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
A host on the segment pings the stack. Having learned the stack's MAC
|
|
323
|
+
from its ARP Announcement, the host sends the Echo Request directly;
|
|
324
|
+
the stack then resolves the *host's* MAC via ARP before replying:
|
|
325
|
+
|
|
326
|
+
Wire capture (`tshark -i tap7`, rebased to the first Echo Request):
|
|
327
|
+
|
|
328
|
+
```text
|
|
329
|
+
0.000 ICMP 192.168.1.10 → 192.168.1.77 Echo (ping) request id=0x626e, seq=1, ttl=64
|
|
330
|
+
0.001 ARP 192.168.1.77 → 192.168.1.10 Who has 192.168.1.10? Tell 192.168.1.77
|
|
331
|
+
0.001 ARP 192.168.1.10 → 192.168.1.77 192.168.1.10 is at a2:4b:a1:00:92:56
|
|
332
|
+
0.001 ICMP 192.168.1.77 → 192.168.1.10 Echo (ping) reply id=0x626e, seq=1, ttl=64
|
|
333
|
+
1.001 ICMP 192.168.1.10 → 192.168.1.77 Echo (ping) request id=0x626e, seq=2, ttl=64
|
|
334
|
+
1.002 ICMP 192.168.1.77 → 192.168.1.10 Echo (ping) reply id=0x626e, seq=2, ttl=64
|
|
335
|
+
2.032 ICMP 192.168.1.10 → 192.168.1.77 Echo (ping) request id=0x626e, seq=3, ttl=64
|
|
336
|
+
2.033 ICMP 192.168.1.77 → 192.168.1.10 Echo (ping) reply id=0x626e, seq=3, ttl=64
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
From the pinging host:
|
|
340
|
+
`3 packets transmitted, 3 received, 0% packet loss; rtt min/avg/max/mdev = 0.693/0.873/1.185/0.221 ms`.
|
|
341
|
+
|
|
342
|
+
#### ICMPv6 Echo over IPv6 (Neighbor Discovery + ping6)
|
|
343
|
+
|
|
344
|
+
**Reproduce:**
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip6-icmp-echo
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The IPv6 counterpart: a host on a ULA pings the stack's IPv6
|
|
351
|
+
address. The host sends the Echo Request directly; the stack
|
|
352
|
+
resolves the *host* with ICMPv6 Neighbor Discovery (Neighbor
|
|
353
|
+
Solicitation → Neighbor Advertisement) before replying:
|
|
354
|
+
|
|
355
|
+
Wire capture (`tshark -i tap7`, rebased to the first Echo
|
|
356
|
+
Request; unrelated LAN router/host traffic filtered out):
|
|
357
|
+
|
|
358
|
+
```text
|
|
359
|
+
0.000 ICMPv6 fd00:1::1 → fd00:1::77 Echo (ping) request id=0x626f, seq=1, hlim=64
|
|
360
|
+
0.001 ICMPv6 fd00:1::77 → ff02::1:ff00:1 Neighbor Solicitation for fd00:1::1 (from 02:00:00:77:77:77)
|
|
361
|
+
0.001 ICMPv6 fd00:1::1 → fd00:1::77 Neighbor Advertisement — fd00:1::1 is at a2:4b:a1:00:92:56
|
|
362
|
+
0.001 ICMPv6 fd00:1::77 → fd00:1::1 Echo (ping) reply id=0x626f, seq=1, hlim=255
|
|
363
|
+
1.001 ICMPv6 fd00:1::1 → fd00:1::77 Echo (ping) request id=0x626f, seq=2, hlim=64
|
|
364
|
+
1.002 ICMPv6 fd00:1::77 → fd00:1::1 Echo (ping) reply id=0x626f, seq=2, hlim=255
|
|
365
|
+
2.044 ICMPv6 fd00:1::1 → fd00:1::77 Echo (ping) request id=0x626f, seq=3, hlim=64
|
|
366
|
+
2.045 ICMPv6 fd00:1::77 → fd00:1::1 Echo (ping) reply id=0x626f, seq=3, hlim=255
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
(`tshark`'s heuristic dissector tags the Echo payload as
|
|
370
|
+
"HiPerConTracer" — a harmless false positive; the frames are plain
|
|
371
|
+
ICMPv6 Echo.)
|
|
372
|
+
|
|
373
|
+
From the pinging host:
|
|
374
|
+
`3 packets transmitted, 3 received, 0% packet loss; rtt min/avg/max/mdev = 0.680/0.882/1.276/0.278 ms`.
|
|
375
|
+
|
|
376
|
+
#### Monkeys over TCP
|
|
377
|
+
|
|
378
|
+
**Reproduce:**
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip4-tcp-monkeys
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
PyTCP ships a matching TCP echo client and service
|
|
385
|
+
(`examples/client__tcp_echo.py` / `examples/service__tcp_echo.py`).
|
|
386
|
+
As a quick end-to-end check the client streams two ASCII-art
|
|
387
|
+
"monkeys" as the payload and the service echoes them back over the
|
|
388
|
+
TCP connection — the original "two monkeys delivered via TCP" demo,
|
|
389
|
+
now reproducible as plain text. Connecting to the service returns
|
|
390
|
+
its banner, then the monkeys make the full round trip through the
|
|
391
|
+
stack's TCP path intact; sending `quit` asks the service to close,
|
|
392
|
+
and PyTCP performs the graceful active close itself:
|
|
393
|
+
|
|
394
|
+
```text
|
|
395
|
+
$ { printf 'malpi\n'; sleep 3; printf 'quit\n'; } | nc 192.168.1.77 7
|
|
396
|
+
***CLIENT OPEN / SERVICE OPEN***
|
|
397
|
+
______AAAA_______________AAAA______
|
|
398
|
+
VVVV VVVV
|
|
399
|
+
(__) (__)
|
|
400
|
+
\ \ / /
|
|
401
|
+
.="=. \ \ / /
|
|
402
|
+
_/.-.-.\_ _ > \ .="=. / <
|
|
403
|
+
( ( o o ) ) )) > \ / \ / <
|
|
404
|
+
|/ " \| // > \\_o_o_// <
|
|
405
|
+
\'---'/ // > ( (_) ) <
|
|
406
|
+
/`---`\ (( >| |<
|
|
407
|
+
/ /_,_\ \ \\ / |\___/| \
|
|
408
|
+
\_\_'__/ \ )) / \_____/ \
|
|
409
|
+
/` /`~\ |// / \
|
|
410
|
+
/ / \ / / o \
|
|
411
|
+
,--`,--'\/\ / ) ___ (
|
|
412
|
+
'-- "--' '--' / / \ \
|
|
413
|
+
( / \ )
|
|
414
|
+
>< ><
|
|
415
|
+
///\ /\\\
|
|
416
|
+
''' '''
|
|
417
|
+
***CLIENT OPEN, SERVICE CLOSING***
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
On the wire (`tshark -i tap7`, rebased to the SYN) — the full
|
|
421
|
+
RFC 9293 exchange, handshake through graceful close:
|
|
422
|
+
|
|
423
|
+
```text
|
|
424
|
+
0.000 TCP 192.168.1.10 → 192.168.1.77 [SYN] Seq=0 MSS=1460 SACK_PERM WS=1024 TSopt
|
|
425
|
+
0.002 ARP 192.168.1.77 → 192.168.1.10 Who has 192.168.1.10? Tell 192.168.1.77
|
|
426
|
+
0.002 ARP 192.168.1.10 → 192.168.1.77 192.168.1.10 is at a2:4b:a1:00:92:56
|
|
427
|
+
0.002 TCP 192.168.1.77 → 192.168.1.10 [SYN,ACK] Seq=0 Ack=1 MSS=1460 SACK_PERM WS=128 TSopt
|
|
428
|
+
0.002 TCP 192.168.1.10 → 192.168.1.77 [ACK] Seq=1 Ack=1
|
|
429
|
+
0.002 TCP 192.168.1.10 → 192.168.1.77 [PSH,ACK] len 6 "malpi\n" (request)
|
|
430
|
+
0.005 TCP 192.168.1.77 → 192.168.1.10 [ACK] len 1448 banner + monkeys, segment 1 (full MSS)
|
|
431
|
+
0.005 TCP 192.168.1.10 → 192.168.1.77 [ACK] Ack=1449
|
|
432
|
+
0.007 TCP 192.168.1.77 → 192.168.1.10 [PSH,ACK] len 146 monkeys, segment 2
|
|
433
|
+
0.007 TCP 192.168.1.10 → 192.168.1.77 [ACK] Ack=1595
|
|
434
|
+
2.999 TCP 192.168.1.10 → 192.168.1.77 [PSH,ACK] len 5 "quit\n" (request)
|
|
435
|
+
3.000 TCP 192.168.1.77 → 192.168.1.10 [PSH,ACK] len 35 "SERVICE CLOSING" banner
|
|
436
|
+
3.000 TCP 192.168.1.10 → 192.168.1.77 [ACK] Ack=1630
|
|
437
|
+
3.003 TCP 192.168.1.77 → 192.168.1.10 [FIN,ACK] PyTCP active close
|
|
438
|
+
3.044 TCP 192.168.1.10 → 192.168.1.77 [ACK] Ack=1631 peer acks the FIN
|
|
439
|
+
6.000 TCP 192.168.1.10 → 192.168.1.77 [FIN,ACK] peer closes its half
|
|
440
|
+
6.001 TCP 192.168.1.77 → 192.168.1.10 [ACK] Ack=13 connection fully closed (no RST)
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The stack negotiates MSS / SACK-permitted / window-scale /
|
|
444
|
+
timestamps on the handshake, resolves the peer's MAC via ARP
|
|
445
|
+
mid-handshake, segments the echoed monkeys to the MSS, tracks the
|
|
446
|
+
peer's cumulative ACKs, and on `quit` performs the RFC 9293 §3.6
|
|
447
|
+
active close — FIN, peer ACK, peer FIN, FIN ACK — a complete TCP
|
|
448
|
+
connection opened, used, and gracefully torn down entirely by
|
|
449
|
+
pure-Python code.
|
|
450
|
+
|
|
451
|
+
#### Monkeys over TCP — over IPv6
|
|
452
|
+
|
|
453
|
+
**Reproduce:**
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip6-tcp-monkeys
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
The same demo, unchanged, over IPv6 (the service bound to a ULA;
|
|
460
|
+
the host resolves it with ICMPv6 Neighbor Discovery instead of
|
|
461
|
+
ARP). The IPv6 MSS is 1440 (vs 1460 on IPv4 — the 20-byte-larger
|
|
462
|
+
fixed header). Same handshake, echo, and RFC 9293 §3.6 graceful
|
|
463
|
+
close:
|
|
464
|
+
|
|
465
|
+
```text
|
|
466
|
+
0.000 ICMPv6 fd00:1::1 → ff02::1:ff00:77 Neighbor Solicitation for fd00:1::77 (from a2:4b:a1:00:92:56)
|
|
467
|
+
0.001 ICMPv6 fd00:1::77 → fd00:1::1 Neighbor Advertisement — fd00:1::77 is at 02:00:00:77:77:77
|
|
468
|
+
0.001 TCP fd00:1::1 → fd00:1::77 [SYN] Seq=0 MSS=1440 SACK_PERM WS=1024 TSopt
|
|
469
|
+
0.003 TCP fd00:1::77 → fd00:1::1 [SYN,ACK] Seq=0 Ack=1 MSS=1440 SACK_PERM WS=128 TSopt
|
|
470
|
+
0.003 TCP fd00:1::1 → fd00:1::77 [ACK] Seq=1 Ack=1
|
|
471
|
+
0.003 TCP fd00:1::1 → fd00:1::77 [PSH,ACK] len 6 "malpi\n" (request)
|
|
472
|
+
0.006 TCP fd00:1::77 → fd00:1::1 [ACK] len 1428 banner + monkeys, segment 1 (full MSS)
|
|
473
|
+
0.006 TCP fd00:1::1 → fd00:1::77 [ACK] Ack=1429
|
|
474
|
+
0.008 TCP fd00:1::77 → fd00:1::1 [PSH,ACK] len 166 monkeys, segment 2
|
|
475
|
+
0.008 TCP fd00:1::1 → fd00:1::77 [ACK] Ack=1595
|
|
476
|
+
2.999 TCP fd00:1::1 → fd00:1::77 [PSH,ACK] len 5 "quit\n" (request)
|
|
477
|
+
3.001 TCP fd00:1::77 → fd00:1::1 [PSH,ACK] len 35 "SERVICE CLOSING" banner
|
|
478
|
+
3.001 TCP fd00:1::1 → fd00:1::77 [ACK] Ack=1630
|
|
479
|
+
3.005 TCP fd00:1::77 → fd00:1::1 [FIN,ACK] PyTCP active close
|
|
480
|
+
3.045 TCP fd00:1::1 → fd00:1::77 [ACK] Ack=1631 peer acks the FIN
|
|
481
|
+
6.001 TCP fd00:1::1 → fd00:1::77 [FIN,ACK] peer closes its half
|
|
482
|
+
6.002 TCP fd00:1::77 → fd00:1::1 [ACK] Ack=13 connection fully closed (no RST)
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
(`tshark` labels the port-7 data segments "ECHO" — a heuristic;
|
|
486
|
+
they are plain TCP.)
|
|
487
|
+
|
|
488
|
+
#### Monkeys over UDP — IPv4 fragmentation
|
|
489
|
+
|
|
490
|
+
**Reproduce:**
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip4-udp-monkeys
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
The same ASCII monkeys, echoed over the UDP service. The reply
|
|
497
|
+
(~1.5 KB) exceeds the 1500-byte link MTU, so the stack
|
|
498
|
+
IPv4-fragments it — the classic "IP fragmentation" demo, captured
|
|
499
|
+
for real.
|
|
500
|
+
|
|
501
|
+
```text
|
|
502
|
+
$ printf 'malpi\n' | nc -u 192.168.1.77 7
|
|
503
|
+
______AAAA_______________AAAA______
|
|
504
|
+
VVVV VVVV
|
|
505
|
+
(__) (__)
|
|
506
|
+
\ \ / /
|
|
507
|
+
.="=. \ \ / /
|
|
508
|
+
_/.-.-.\_ _ > \ .="=. / <
|
|
509
|
+
( ( o o ) ) )) > \ / \ / <
|
|
510
|
+
|/ " \| // > \\_o_o_// <
|
|
511
|
+
\'---'/ // > ( (_) ) <
|
|
512
|
+
/`---`\ (( >| |<
|
|
513
|
+
/ /_,_\ \ \\ / |\___/| \
|
|
514
|
+
\_\_'__/ \ )) / \_____/ \
|
|
515
|
+
/` /`~\ |// / \
|
|
516
|
+
/ / \ / / o \
|
|
517
|
+
,--`,--'\/\ / ) ___ (
|
|
518
|
+
'-- "--' '--' / / \ \
|
|
519
|
+
( / \ )
|
|
520
|
+
>< ><
|
|
521
|
+
///\ /\\\
|
|
522
|
+
''' '''
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
On the wire (`tshark -i tap7`, rebased to the request; the
|
|
526
|
+
summary carries the IPv4 fragmentation fields — IP-id, MF,
|
|
527
|
+
frag-offset):
|
|
528
|
+
|
|
529
|
+
```text
|
|
530
|
+
0.000 UDP 192.168.1.10 → 192.168.1.77 id=0x9655 MF=0 off=0 UDP "malpi\n" request (14 B)
|
|
531
|
+
0.001 ARP 192.168.1.77 → 192.168.1.10 Who has 192.168.1.10? Tell 192.168.1.77
|
|
532
|
+
0.001 ARP 192.168.1.10 → 192.168.1.77 192.168.1.10 is at a2:4b:a1:00:92:56
|
|
533
|
+
0.001 UDP 192.168.1.77 → 192.168.1.10 id=0x0001 MF=1 off=0 fragment 1 — UDP header + first 1480 B
|
|
534
|
+
0.002 UDP 192.168.1.77 → 192.168.1.10 id=0x0001 MF=0 off=185 fragment 2 — final 89 B (offset 185×8 = 1480)
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
The oversized UDP datagram is split into two IPv4 fragments sharing
|
|
538
|
+
one IP id; the peer's kernel reassembles them and `nc -u` prints
|
|
539
|
+
the monkeys. The first datagram is held in the per-neighbour queue
|
|
540
|
+
until the ARP reply resolves the peer's MAC (RFC 1122 §2.3.2.2),
|
|
541
|
+
then both fragments are flushed in order — a fragmented datagram
|
|
542
|
+
delivered to a cold neighbour, lost by neither the DF bit nor a
|
|
543
|
+
single-slot queue.
|
|
544
|
+
|
|
545
|
+
#### Monkeys over UDP — over IPv6
|
|
546
|
+
|
|
547
|
+
**Reproduce:**
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip6-udp-monkeys
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
The same oversized echo over IPv6. IPv6 fragments differently from
|
|
554
|
+
IPv4: the base header is never modified — the source inserts a
|
|
555
|
+
**Fragment extension header** (RFC 8200 §4.5), and only the source
|
|
556
|
+
may fragment. The stack resolves the peer via ICMPv6 Neighbor
|
|
557
|
+
Discovery (NS → NA), then emits the ~1.5 KB reply as two IPv6
|
|
558
|
+
fragments sharing one identification:
|
|
559
|
+
|
|
560
|
+
```text
|
|
561
|
+
0.000 UDP fd00:1::1 → fd00:1::77 "malpi\n" request
|
|
562
|
+
0.001 ICMPv6 fd00:1::77 → ff02::1:ff00:1 Neighbor Solicitation for fd00:1::1 (from 02:00:00:77:77:77)
|
|
563
|
+
0.001 ICMPv6 fd00:1::1 → fd00:1::77 Neighbor Advertisement — fd00:1::1 is at a2:4b:a1:00:92:56
|
|
564
|
+
0.002 IPv6 fd00:1::77 → fd00:1::1 Fragment header: off=0 more=1 ident=0xc6713a45 next=UDP (fragment 1)
|
|
565
|
+
0.002 UDP fd00:1::77 → fd00:1::1 final fragment — reassembles to the 1569-byte datagram
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
(`tshark` labels the port-7 datagrams "ECHO" — a heuristic; they
|
|
569
|
+
are plain UDP. The 1561-byte reply + 8-byte UDP header = 1569 B,
|
|
570
|
+
over the 1500-byte link MTU, so the stack splits it across the two
|
|
571
|
+
fragments above.)
|
|
572
|
+
|
|
573
|
+
#### Inbound IPv4 reassembly (oversized ping)
|
|
574
|
+
|
|
575
|
+
**Reproduce:**
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip4-icmp-frag-rx --count 1
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The receive-side counterpart of the fragmentation demos. The host
|
|
582
|
+
sends a 4000-byte `ping`, which its kernel splits into three IPv4
|
|
583
|
+
fragments. The stack **reassembles** them into one Echo Request,
|
|
584
|
+
then replies with a 4000-byte Echo Reply that it **itself
|
|
585
|
+
fragments** into three:
|
|
586
|
+
|
|
587
|
+
```text
|
|
588
|
+
0.000 IPv4 192.168.1.10 → 192.168.1.77 id=0xf29f MF=1 off=0 Echo Request — fragment 1/3
|
|
589
|
+
0.000 IPv4 192.168.1.10 → 192.168.1.77 id=0xf29f MF=1 off=185 fragment 2/3 (off 185×8 = 1480 B)
|
|
590
|
+
0.000 IPv4 192.168.1.10 → 192.168.1.77 id=0xf29f MF=0 off=370 fragment 3/3 → reassembles to Echo Request id=0x6271, seq=1
|
|
591
|
+
0.001 ARP 192.168.1.77 → 192.168.1.10 Who has 192.168.1.10? Tell 192.168.1.77
|
|
592
|
+
0.001 ARP 192.168.1.10 → 192.168.1.77 192.168.1.10 is at a2:4b:a1:00:92:56
|
|
593
|
+
0.002 IPv4 192.168.1.77 → 192.168.1.10 id=0x0001 MF=1 off=0 Echo Reply — fragment 1/3
|
|
594
|
+
0.002 IPv4 192.168.1.77 → 192.168.1.10 id=0x0001 MF=1 off=185 fragment 2/3
|
|
595
|
+
0.002 IPv4 192.168.1.77 → 192.168.1.10 id=0x0001 MF=0 off=370 fragment 3/3 → Echo Reply id=0x6271, seq=1
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
From the pinging host:
|
|
599
|
+
`1 packets transmitted, 1 received, 0% packet loss; rtt min/avg/max/mdev = 2.048/2.048/2.048/0.000 ms`
|
|
600
|
+
(`4008 bytes from 192.168.1.77` — the full 4000-byte payload made
|
|
601
|
+
the round trip, reassembled on both ends).
|
|
602
|
+
|
|
603
|
+
#### DHCPv4 client lease
|
|
604
|
+
|
|
605
|
+
**Reproduce** (needs a DHCPv4 server reachable on the bridge):
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture ip4-dhcp
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
With no static IPv4 configured, the stack runs its DHCPv4 client:
|
|
612
|
+
the full DORA exchange (Discover → Offer → Request → ACK), and
|
|
613
|
+
then — because the address is unverified — RFC 5227 Address
|
|
614
|
+
Conflict Detection on the *DHCP-assigned* address before it is
|
|
615
|
+
used. A randomized RFC 2131 initial-desync delay (~6.8 s here)
|
|
616
|
+
precedes the first Discover:
|
|
617
|
+
|
|
618
|
+
```text
|
|
619
|
+
0.000 DHCP 0.0.0.0 → 255.255.255.255 DHCP Discover xid 0x3207aee
|
|
620
|
+
0.000 DHCP 192.168.1.1 → 255.255.255.255 DHCP Offer xid 0x3207aee (offers 192.168.1.145)
|
|
621
|
+
3.002 DHCP 0.0.0.0 → 255.255.255.255 DHCP Request xid 0x3207aee (requesting 192.168.1.145)
|
|
622
|
+
3.002 DHCP 192.168.1.1 → 255.255.255.255 DHCP ACK xid 0x3207aee (lease 3600 s)
|
|
623
|
+
3.810 ARP 0.0.0.0 → 192.168.1.145 ARP Probe — Who has 192.168.1.145? (RFC 5227 ACD on the leased address)
|
|
624
|
+
5.599 ARP 0.0.0.0 → 192.168.1.145 ARP Probe — Who has 192.168.1.145?
|
|
625
|
+
6.891 ARP 0.0.0.0 → 192.168.1.145 ARP Probe — Who has 192.168.1.145?
|
|
626
|
+
10.252 ARP 192.168.1.145 → 192.168.1.145 ARP Announcement for 192.168.1.145
|
|
627
|
+
12.252 ARP 192.168.1.145 → 192.168.1.145 ARP Announcement for 192.168.1.145
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
Stack log:
|
|
631
|
+
|
|
632
|
+
```text
|
|
633
|
+
0015.05 | DHCP4 | Initial desync delay: 6.83s
|
|
634
|
+
0021.89 | DHCP4 | TX - DHCPv4 Request ... [message_type Discover ...]
|
|
635
|
+
0021.89 | DHCP4 | RX - DHCPv4 Reply ... yiaddr 192.168.1.145 ... [message_type Offer, server_id 192.168.1.1 ...]
|
|
636
|
+
0024.89 | DHCP4 | TX - DHCPv4 Request ... [message_type Request, server_id 192.168.1.1, req_ip_addr 192.168.1.145 ...]
|
|
637
|
+
0024.89 | DHCP4 | RX - DHCPv4 Reply ... [message_type ACK, lease_time 3600 ...]
|
|
638
|
+
0032.14 | DHCP4 | Lease acquired: 192.168.1.145/24 (lease_time=3600s, server=192.168.1.1)
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
#### TCP under packet loss — retransmission & recovery
|
|
642
|
+
|
|
643
|
+
**Reproduce** (asserts the connection still completes — exits
|
|
644
|
+
non-zero if it does not):
|
|
645
|
+
|
|
646
|
+
```bash
|
|
647
|
+
sudo PYTHONPATH=. venv/bin/python -m tools.capture \
|
|
648
|
+
--loss 20 --expect-wire '\[FIN, ACK\]' ip4-tcp-monkeys
|
|
649
|
+
# … → [PASS] wire: /\[FIN, ACK\]/ , exit 0
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Every example above runs on a clean bridge, so the loss-recovery
|
|
653
|
+
machinery never fires. Driven through a `tc netem loss 20%`
|
|
654
|
+
qdisc, the same TCP monkeys exchange has segments dropped in both
|
|
655
|
+
directions — and the stack recovers: it retransmits its own
|
|
656
|
+
segments on RTO, the peer SACKs the holes, and the connection
|
|
657
|
+
still completes and closes cleanly (no RST). One representative
|
|
658
|
+
run (loss is random — every run drops different packets; the
|
|
659
|
+
invariant is that it *completes*), rebased to the SYN:
|
|
660
|
+
|
|
661
|
+
```text
|
|
662
|
+
0.000 TCP 192.168.1.10 → 192.168.1.77 [SYN] Seq=0 MSS=1460 SACK_PERM WS=1024
|
|
663
|
+
0.003 TCP 192.168.1.77 → 192.168.1.10 [SYN,ACK] Seq=0 Ack=1
|
|
664
|
+
0.003 TCP 192.168.1.10 → 192.168.1.77 [ACK]
|
|
665
|
+
0.006 TCP 192.168.1.77 → 192.168.1.10 [PSH,ACK] open banner, Len 33
|
|
666
|
+
0.035 TCP 192.168.1.77 → 192.168.1.10 [PSH,ACK] [TCP Retransmission] Seq=1 Len 33 (banner drop → RTO resend)
|
|
667
|
+
0.035 TCP 192.168.1.10 → 192.168.1.77 [ACK] [Previous segment not captured] SACK SLE=1 SRE=34
|
|
668
|
+
0.036 TCP 192.168.1.77 → 192.168.1.10 [ACK] [TCP Dup ACK]
|
|
669
|
+
0.208 TCP 192.168.1.10 → 192.168.1.77 [PSH,ACK] [TCP Retransmission] "malpi\n" request resent
|
|
670
|
+
0.211 TCP 192.168.1.77 → 192.168.1.10 [PSH,ACK] monkeys, segment 1
|
|
671
|
+
0.279 TCP 192.168.1.77 → 192.168.1.10 [PSH,ACK] [TCP Retransmission] Seq=1482 Len 113 monkeys seg 2 resent
|
|
672
|
+
0.279 TCP 192.168.1.10 → 192.168.1.77 [ACK] SACK SLE=1482 SRE=1595
|
|
673
|
+
3.210 TCP 192.168.1.10 → 192.168.1.77 [PSH,ACK] "quit\n" request
|
|
674
|
+
4.001 TCP 192.168.1.77 → 192.168.1.10 [PSH,ACK] [TCP Retransmission] "SERVICE CLOSING" banner resent
|
|
675
|
+
4.004 TCP 192.168.1.77 → 192.168.1.10 [FIN,ACK] PyTCP active close
|
|
676
|
+
4.044 TCP 192.168.1.10 → 192.168.1.77 [ACK] peer acks the FIN
|
|
677
|
+
6.000 TCP 192.168.1.10 → 192.168.1.77 [FIN,ACK] peer closes its half
|
|
678
|
+
6.001 TCP 192.168.1.77 → 192.168.1.10 [ACK] connection fully closed (no RST)
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
`--loss` (and `--delay-ms` / `--reorder` / `--duplicate` /
|
|
682
|
+
`--corrupt`) plus the `--expect-log` / `--expect-wire` /
|
|
683
|
+
`--expect-client` assertions are global options that go before
|
|
684
|
+
*any* scenario, so any capture can be turned into a loss /
|
|
685
|
+
latency e2e check.
|