@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3
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.
- package/bin/kgraph-launcher +15 -3
- package/lib/kgraph/scripts/build-bundle.sh +17 -4
- package/lib/site-packages/outcome/__init__.py +20 -0
- package/lib/site-packages/outcome/_impl.py +239 -0
- package/lib/site-packages/outcome/_util.py +33 -0
- package/lib/site-packages/outcome/_version.py +7 -0
- package/lib/site-packages/outcome/py.typed +0 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sniffio/__init__.py +17 -0
- package/lib/site-packages/sniffio/_impl.py +95 -0
- package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
- package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
- package/lib/site-packages/sniffio/_version.py +3 -0
- package/lib/site-packages/sniffio/py.typed +0 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sortedcontainers/__init__.py +74 -0
- package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
- package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
- package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/trio/__init__.py +133 -0
- package/lib/site-packages/trio/__main__.py +3 -0
- package/lib/site-packages/trio/_abc.py +714 -0
- package/lib/site-packages/trio/_channel.py +610 -0
- package/lib/site-packages/trio/_core/__init__.py +94 -0
- package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
- package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
- package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
- package/lib/site-packages/trio/_core/_exceptions.py +169 -0
- package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
- package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
- package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
- package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
- package/lib/site-packages/trio/_core/_generated_run.py +269 -0
- package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
- package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
- package/lib/site-packages/trio/_core/_io_common.py +31 -0
- package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
- package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
- package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
- package/lib/site-packages/trio/_core/_ki.py +271 -0
- package/lib/site-packages/trio/_core/_local.py +104 -0
- package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
- package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
- package/lib/site-packages/trio/_core/_run.py +3148 -0
- package/lib/site-packages/trio/_core/_run_context.py +15 -0
- package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
- package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
- package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
- package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
- package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
- package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
- package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
- package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
- package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
- package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
- package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
- package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
- package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
- package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
- package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
- package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
- package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
- package/lib/site-packages/trio/_core/_traps.py +318 -0
- package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
- package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
- package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
- package/lib/site-packages/trio/_deprecate.py +171 -0
- package/lib/site-packages/trio/_dtls.py +1380 -0
- package/lib/site-packages/trio/_file_io.py +513 -0
- package/lib/site-packages/trio/_highlevel_generic.py +125 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
- package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
- package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
- package/lib/site-packages/trio/_highlevel_socket.py +423 -0
- package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
- package/lib/site-packages/trio/_path.py +289 -0
- package/lib/site-packages/trio/_repl.py +159 -0
- package/lib/site-packages/trio/_signals.py +185 -0
- package/lib/site-packages/trio/_socket.py +1326 -0
- package/lib/site-packages/trio/_ssl.py +964 -0
- package/lib/site-packages/trio/_subprocess.py +1178 -0
- package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
- package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
- package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
- package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
- package/lib/site-packages/trio/_sync.py +908 -0
- package/lib/site-packages/trio/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
- package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
- package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
- package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
- package/lib/site-packages/trio/_tests/test_abc.py +72 -0
- package/lib/site-packages/trio/_tests/test_channel.py +750 -0
- package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
- package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
- package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
- package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
- package/lib/site-packages/trio/_tests/test_exports.py +626 -0
- package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
- package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
- package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
- package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
- package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
- package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
- package/lib/site-packages/trio/_tests/test_path.py +279 -0
- package/lib/site-packages/trio/_tests/test_repl.py +428 -0
- package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
- package/lib/site-packages/trio/_tests/test_signals.py +186 -0
- package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
- package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
- package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
- package/lib/site-packages/trio/_tests/test_sync.py +735 -0
- package/lib/site-packages/trio/_tests/test_testing.py +682 -0
- package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
- package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
- package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
- package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
- package/lib/site-packages/trio/_tests/test_trio.py +8 -0
- package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
- package/lib/site-packages/trio/_tests/test_util.py +349 -0
- package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
- package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
- package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
- package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
- package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
- package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
- package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
- package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
- package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
- package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
- package/lib/site-packages/trio/_threads.py +610 -0
- package/lib/site-packages/trio/_timeouts.py +197 -0
- package/lib/site-packages/trio/_tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
- package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
- package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
- package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
- package/lib/site-packages/trio/_unix_pipes.py +197 -0
- package/lib/site-packages/trio/_util.py +385 -0
- package/lib/site-packages/trio/_version.py +3 -0
- package/lib/site-packages/trio/_wait_for_object.py +67 -0
- package/lib/site-packages/trio/_windows_pipes.py +144 -0
- package/lib/site-packages/trio/abc.py +23 -0
- package/lib/site-packages/trio/from_thread.py +13 -0
- package/lib/site-packages/trio/lowlevel.py +95 -0
- package/lib/site-packages/trio/py.typed +0 -0
- package/lib/site-packages/trio/socket.py +602 -0
- package/lib/site-packages/trio/testing/__init__.py +58 -0
- package/lib/site-packages/trio/testing/_check_streams.py +570 -0
- package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
- package/lib/site-packages/trio/testing/_fake_net.py +584 -0
- package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
- package/lib/site-packages/trio/testing/_network.py +36 -0
- package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
- package/lib/site-packages/trio/testing/_sequencer.py +87 -0
- package/lib/site-packages/trio/testing/_trio_test.py +50 -0
- package/lib/site-packages/trio/to_thread.py +4 -0
- package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
- package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
- package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
- package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
- package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
- package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from itertools import count
|
|
6
|
+
from typing import TYPE_CHECKING, NoReturn
|
|
7
|
+
|
|
8
|
+
import attrs
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from trio._tests.pytest_plugin import skip_if_optional_else_raise
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import trustme
|
|
15
|
+
from OpenSSL import SSL
|
|
16
|
+
except ImportError as error:
|
|
17
|
+
skip_if_optional_else_raise(error)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
import trio
|
|
21
|
+
import trio.testing
|
|
22
|
+
from trio import DTLSChannel, DTLSEndpoint
|
|
23
|
+
from trio.testing._fake_net import FakeNet, UDPPacket
|
|
24
|
+
|
|
25
|
+
from .._core._tests.tutil import binds_ipv6, gc_collect_harder, slow
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from collections.abc import AsyncGenerator
|
|
29
|
+
|
|
30
|
+
ca = trustme.CA()
|
|
31
|
+
server_cert = ca.issue_cert("example.com")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def server_ctx() -> SSL.Context:
|
|
36
|
+
ctx = SSL.Context(SSL.DTLS_METHOD)
|
|
37
|
+
server_cert.configure_cert(ctx)
|
|
38
|
+
return ctx
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def client_ctx_fn() -> SSL.Context:
|
|
42
|
+
ctx = SSL.Context(SSL.DTLS_METHOD)
|
|
43
|
+
ca.configure_trust(ctx)
|
|
44
|
+
return ctx
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def client_ctx() -> SSL.Context:
|
|
49
|
+
return client_ctx_fn()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
parametrize_ipv6 = pytest.mark.parametrize(
|
|
53
|
+
"ipv6",
|
|
54
|
+
[False, pytest.param(True, marks=binds_ipv6)],
|
|
55
|
+
ids=["ipv4", "ipv6"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def endpoint(**kwargs: int | bool) -> DTLSEndpoint:
|
|
60
|
+
ipv6 = kwargs.pop("ipv6", False)
|
|
61
|
+
family = trio.socket.AF_INET6 if ipv6 else trio.socket.AF_INET
|
|
62
|
+
sock = trio.socket.socket(type=trio.socket.SOCK_DGRAM, family=family)
|
|
63
|
+
return DTLSEndpoint(sock, **kwargs)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@asynccontextmanager
|
|
67
|
+
async def dtls_echo_server(
|
|
68
|
+
*,
|
|
69
|
+
server_ctx: SSL.Context,
|
|
70
|
+
autocancel: bool = True,
|
|
71
|
+
mtu: int | None = None,
|
|
72
|
+
ipv6: bool = False,
|
|
73
|
+
) -> AsyncGenerator[tuple[DTLSEndpoint, tuple[str, int]], None]:
|
|
74
|
+
with endpoint(ipv6=ipv6) as server:
|
|
75
|
+
localhost = "::1" if ipv6 else "127.0.0.1"
|
|
76
|
+
await server.socket.bind((localhost, 0))
|
|
77
|
+
async with trio.open_nursery() as nursery:
|
|
78
|
+
|
|
79
|
+
async def echo_handler(dtls_channel: DTLSChannel) -> None:
|
|
80
|
+
print(
|
|
81
|
+
"echo handler started: "
|
|
82
|
+
f"server {dtls_channel.endpoint.socket.getsockname()!r} "
|
|
83
|
+
f"client {dtls_channel.peer_address!r}",
|
|
84
|
+
)
|
|
85
|
+
if mtu is not None:
|
|
86
|
+
dtls_channel.set_ciphertext_mtu(mtu)
|
|
87
|
+
try:
|
|
88
|
+
print("server starting do_handshake")
|
|
89
|
+
await dtls_channel.do_handshake()
|
|
90
|
+
print("server finished do_handshake")
|
|
91
|
+
# no branch for leaving this for loop because we only leave
|
|
92
|
+
# a channel by cancellation.
|
|
93
|
+
async for packet in dtls_channel: # pragma: no branch
|
|
94
|
+
print(f"echoing {packet!r} -> {dtls_channel.peer_address!r}")
|
|
95
|
+
await dtls_channel.send(packet)
|
|
96
|
+
except trio.BrokenResourceError: # pragma: no cover
|
|
97
|
+
print("echo handler channel broken")
|
|
98
|
+
|
|
99
|
+
await nursery.start(server.serve, server_ctx, echo_handler)
|
|
100
|
+
|
|
101
|
+
yield server, server.socket.getsockname()
|
|
102
|
+
|
|
103
|
+
if autocancel:
|
|
104
|
+
nursery.cancel_scope.cancel()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@parametrize_ipv6
|
|
108
|
+
async def test_smoke(
|
|
109
|
+
ipv6: bool, server_ctx: SSL.Context, client_ctx: SSL.Context
|
|
110
|
+
) -> None:
|
|
111
|
+
async with dtls_echo_server(ipv6=ipv6, server_ctx=server_ctx) as (
|
|
112
|
+
_server_endpoint,
|
|
113
|
+
address,
|
|
114
|
+
):
|
|
115
|
+
with endpoint(ipv6=ipv6) as client_endpoint:
|
|
116
|
+
client_channel = client_endpoint.connect(address, client_ctx)
|
|
117
|
+
with pytest.raises(trio.NeedHandshakeError):
|
|
118
|
+
client_channel.get_cleartext_mtu()
|
|
119
|
+
|
|
120
|
+
await client_channel.do_handshake()
|
|
121
|
+
await client_channel.send(b"hello")
|
|
122
|
+
assert await client_channel.receive() == b"hello"
|
|
123
|
+
await client_channel.send(b"goodbye")
|
|
124
|
+
assert await client_channel.receive() == b"goodbye"
|
|
125
|
+
|
|
126
|
+
with pytest.raises(
|
|
127
|
+
ValueError,
|
|
128
|
+
match=r"^openssl doesn't support sending empty DTLS packets$",
|
|
129
|
+
):
|
|
130
|
+
await client_channel.send(b"")
|
|
131
|
+
|
|
132
|
+
client_channel.set_ciphertext_mtu(1234)
|
|
133
|
+
cleartext_mtu_1234 = client_channel.get_cleartext_mtu()
|
|
134
|
+
client_channel.set_ciphertext_mtu(4321)
|
|
135
|
+
assert client_channel.get_cleartext_mtu() > cleartext_mtu_1234
|
|
136
|
+
client_channel.set_ciphertext_mtu(1234)
|
|
137
|
+
assert client_channel.get_cleartext_mtu() == cleartext_mtu_1234
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@slow
|
|
141
|
+
async def test_handshake_over_terrible_network(
|
|
142
|
+
autojump_clock: trio.testing.MockClock,
|
|
143
|
+
server_ctx: SSL.Context,
|
|
144
|
+
) -> None:
|
|
145
|
+
HANDSHAKES = 100
|
|
146
|
+
r = random.Random(0)
|
|
147
|
+
fn = FakeNet()
|
|
148
|
+
fn.enable()
|
|
149
|
+
# avoid spurious timeouts on slow machines
|
|
150
|
+
autojump_clock.autojump_threshold = 0.001
|
|
151
|
+
|
|
152
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
153
|
+
async with trio.open_nursery() as nursery:
|
|
154
|
+
|
|
155
|
+
async def route_packet(packet: UDPPacket) -> None:
|
|
156
|
+
while True:
|
|
157
|
+
op = r.choices(
|
|
158
|
+
["deliver", "drop", "dupe", "delay"],
|
|
159
|
+
weights=[0.7, 0.1, 0.1, 0.1],
|
|
160
|
+
)[0]
|
|
161
|
+
print(f"{packet.source} -> {packet.destination}: {op}")
|
|
162
|
+
if op == "drop":
|
|
163
|
+
return
|
|
164
|
+
elif op == "dupe":
|
|
165
|
+
fn.send_packet(packet)
|
|
166
|
+
elif op == "delay":
|
|
167
|
+
await trio.sleep(r.random() * 3)
|
|
168
|
+
# I wanted to test random packet corruption too, but it turns out
|
|
169
|
+
# openssl has a bug in the following scenario:
|
|
170
|
+
#
|
|
171
|
+
# - client sends ClientHello
|
|
172
|
+
# - server sends HelloVerifyRequest with cookie -- but cookie is
|
|
173
|
+
# invalid b/c either the ClientHello or HelloVerifyRequest was
|
|
174
|
+
# corrupted
|
|
175
|
+
# - client re-sends ClientHello with invalid cookie
|
|
176
|
+
# - server replies with new HelloVerifyRequest and correct cookie
|
|
177
|
+
#
|
|
178
|
+
# At this point, the client *should* switch to the new, valid
|
|
179
|
+
# cookie. But OpenSSL doesn't; it stubbornly insists on re-sending
|
|
180
|
+
# the original, invalid cookie over and over. In theory we could
|
|
181
|
+
# work around this by detecting cookie changes and starting over
|
|
182
|
+
# with a whole new SSL object, but (a) it doesn't seem worth it, (b)
|
|
183
|
+
# when I tried then I ran into another issue where OpenSSL got stuck
|
|
184
|
+
# in an infinite loop sending alerts over and over, which I didn't
|
|
185
|
+
# dig into because see (a).
|
|
186
|
+
#
|
|
187
|
+
# elif op == "distort":
|
|
188
|
+
# payload = bytearray(packet.payload)
|
|
189
|
+
# payload[r.randrange(len(payload))] ^= 1 << r.randrange(8)
|
|
190
|
+
# packet = attrs.evolve(packet, payload=payload)
|
|
191
|
+
else:
|
|
192
|
+
assert op == "deliver"
|
|
193
|
+
print(
|
|
194
|
+
f"{packet.source} -> {packet.destination}: delivered"
|
|
195
|
+
f" {packet.payload.hex()}",
|
|
196
|
+
)
|
|
197
|
+
fn.deliver_packet(packet)
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
def route_packet_wrapper(packet: UDPPacket) -> None:
|
|
201
|
+
try: # noqa: SIM105 # suppressible-exception
|
|
202
|
+
nursery.start_soon(route_packet, packet)
|
|
203
|
+
except RuntimeError: # pragma: no cover
|
|
204
|
+
# We're exiting the nursery, so any remaining packets can just get
|
|
205
|
+
# dropped
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
fn.route_packet = route_packet_wrapper # type: ignore[assignment] # TODO: Fix FakeNet typing
|
|
209
|
+
|
|
210
|
+
for i in range(HANDSHAKES):
|
|
211
|
+
print("#" * 80)
|
|
212
|
+
print("#" * 80)
|
|
213
|
+
print("#" * 80)
|
|
214
|
+
with endpoint() as client_endpoint:
|
|
215
|
+
client = client_endpoint.connect(address, client_ctx_fn())
|
|
216
|
+
print("client starting do_handshake")
|
|
217
|
+
await client.do_handshake()
|
|
218
|
+
print("client finished do_handshake")
|
|
219
|
+
msg = str(i).encode()
|
|
220
|
+
# Make multiple attempts to send data, because the network might
|
|
221
|
+
# drop it
|
|
222
|
+
while True:
|
|
223
|
+
with trio.move_on_after(10) as cscope:
|
|
224
|
+
await client.send(msg)
|
|
225
|
+
assert await client.receive() == msg
|
|
226
|
+
if not cscope.cancelled_caught:
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
async def test_implicit_handshake(
|
|
231
|
+
server_ctx: SSL.Context, client_ctx: SSL.Context
|
|
232
|
+
) -> None:
|
|
233
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
234
|
+
with endpoint() as client_endpoint:
|
|
235
|
+
client = client_endpoint.connect(address, client_ctx)
|
|
236
|
+
|
|
237
|
+
# Implicit handshake
|
|
238
|
+
await client.send(b"xyz")
|
|
239
|
+
assert await client.receive() == b"xyz"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
async def test_full_duplex(server_ctx: SSL.Context, client_ctx: SSL.Context) -> None:
|
|
243
|
+
# Tests simultaneous send/receive, and also multiple methods implicitly invoking
|
|
244
|
+
# do_handshake simultaneously.
|
|
245
|
+
with endpoint() as server_endpoint, endpoint() as client_endpoint:
|
|
246
|
+
await server_endpoint.socket.bind(("127.0.0.1", 0))
|
|
247
|
+
async with trio.open_nursery() as server_nursery:
|
|
248
|
+
|
|
249
|
+
async def handler(channel: DTLSChannel) -> None:
|
|
250
|
+
async with trio.open_nursery() as nursery:
|
|
251
|
+
nursery.start_soon(channel.send, b"from server")
|
|
252
|
+
nursery.start_soon(channel.receive)
|
|
253
|
+
|
|
254
|
+
await server_nursery.start(server_endpoint.serve, server_ctx, handler)
|
|
255
|
+
|
|
256
|
+
client = client_endpoint.connect(
|
|
257
|
+
server_endpoint.socket.getsockname(),
|
|
258
|
+
client_ctx,
|
|
259
|
+
)
|
|
260
|
+
async with trio.open_nursery() as nursery:
|
|
261
|
+
nursery.start_soon(client.send, b"from client")
|
|
262
|
+
nursery.start_soon(client.receive)
|
|
263
|
+
|
|
264
|
+
server_nursery.cancel_scope.cancel()
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
async def test_channel_closing(
|
|
268
|
+
server_ctx: SSL.Context, client_ctx: SSL.Context
|
|
269
|
+
) -> None:
|
|
270
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
271
|
+
with endpoint() as client_endpoint:
|
|
272
|
+
client = client_endpoint.connect(address, client_ctx)
|
|
273
|
+
await client.do_handshake()
|
|
274
|
+
client.close()
|
|
275
|
+
|
|
276
|
+
with pytest.raises(trio.ClosedResourceError):
|
|
277
|
+
await client.send(b"abc")
|
|
278
|
+
with pytest.raises(trio.ClosedResourceError):
|
|
279
|
+
await client.receive()
|
|
280
|
+
|
|
281
|
+
# close is idempotent
|
|
282
|
+
client.close()
|
|
283
|
+
# can also aclose
|
|
284
|
+
await client.aclose()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
async def test_serve_exits_cleanly_on_close(server_ctx: SSL.Context) -> None:
|
|
288
|
+
async with dtls_echo_server(autocancel=False, server_ctx=server_ctx) as (
|
|
289
|
+
server_endpoint,
|
|
290
|
+
_address,
|
|
291
|
+
):
|
|
292
|
+
server_endpoint.close()
|
|
293
|
+
# Testing that the nursery exits even without being cancelled
|
|
294
|
+
# close is idempotent
|
|
295
|
+
server_endpoint.close()
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
async def test_client_multiplex(server_ctx: SSL.Context) -> None:
|
|
299
|
+
async with (
|
|
300
|
+
dtls_echo_server(server_ctx=server_ctx) as (_, address1),
|
|
301
|
+
dtls_echo_server(server_ctx=server_ctx) as (_, address2),
|
|
302
|
+
):
|
|
303
|
+
with endpoint() as client_endpoint:
|
|
304
|
+
client1 = client_endpoint.connect(address1, client_ctx_fn())
|
|
305
|
+
client2 = client_endpoint.connect(address2, client_ctx_fn())
|
|
306
|
+
|
|
307
|
+
await client1.send(b"abc")
|
|
308
|
+
await client2.send(b"xyz")
|
|
309
|
+
assert await client2.receive() == b"xyz"
|
|
310
|
+
assert await client1.receive() == b"abc"
|
|
311
|
+
|
|
312
|
+
client_endpoint.close()
|
|
313
|
+
|
|
314
|
+
with pytest.raises(trio.ClosedResourceError):
|
|
315
|
+
await client1.send(b"xxx")
|
|
316
|
+
with pytest.raises(trio.ClosedResourceError):
|
|
317
|
+
await client2.receive()
|
|
318
|
+
with pytest.raises(trio.ClosedResourceError):
|
|
319
|
+
client_endpoint.connect(address1, client_ctx_fn())
|
|
320
|
+
|
|
321
|
+
async def null_handler(_: object) -> None: # pragma: no cover
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
async with trio.open_nursery() as nursery:
|
|
325
|
+
with pytest.raises(trio.ClosedResourceError):
|
|
326
|
+
await nursery.start(client_endpoint.serve, server_ctx, null_handler)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
async def test_dtls_over_dgram_only() -> None:
|
|
330
|
+
with trio.socket.socket() as s:
|
|
331
|
+
with pytest.raises(ValueError, match=r"^DTLS requires a SOCK_DGRAM socket$"):
|
|
332
|
+
DTLSEndpoint(s)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
async def test_double_serve(server_ctx: SSL.Context) -> None:
|
|
336
|
+
async def null_handler(_: object) -> None: # pragma: no cover
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
with endpoint() as server_endpoint:
|
|
340
|
+
await server_endpoint.socket.bind(("127.0.0.1", 0))
|
|
341
|
+
async with trio.open_nursery() as nursery:
|
|
342
|
+
await nursery.start(server_endpoint.serve, server_ctx, null_handler)
|
|
343
|
+
with pytest.raises(trio.BusyResourceError):
|
|
344
|
+
await nursery.start(server_endpoint.serve, server_ctx, null_handler)
|
|
345
|
+
|
|
346
|
+
nursery.cancel_scope.cancel()
|
|
347
|
+
|
|
348
|
+
async with trio.open_nursery() as nursery:
|
|
349
|
+
await nursery.start(server_endpoint.serve, server_ctx, null_handler)
|
|
350
|
+
nursery.cancel_scope.cancel()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
async def test_connect_to_non_server(
|
|
354
|
+
autojump_clock: trio.abc.Clock, client_ctx: SSL.Context
|
|
355
|
+
) -> None:
|
|
356
|
+
fn = FakeNet()
|
|
357
|
+
fn.enable()
|
|
358
|
+
with endpoint() as client1, endpoint() as client2:
|
|
359
|
+
await client1.socket.bind(("127.0.0.1", 0))
|
|
360
|
+
# This should just time out
|
|
361
|
+
with trio.move_on_after(100) as cscope:
|
|
362
|
+
channel = client2.connect(client1.socket.getsockname(), client_ctx)
|
|
363
|
+
await channel.do_handshake()
|
|
364
|
+
assert cscope.cancelled_caught
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@pytest.mark.parametrize("buffer_size", [10, 20])
|
|
368
|
+
async def test_incoming_buffer_overflow(
|
|
369
|
+
autojump_clock: trio.abc.Clock,
|
|
370
|
+
server_ctx: SSL.Context,
|
|
371
|
+
client_ctx: SSL.Context,
|
|
372
|
+
buffer_size: int,
|
|
373
|
+
) -> None:
|
|
374
|
+
fn = FakeNet()
|
|
375
|
+
fn.enable()
|
|
376
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
377
|
+
with endpoint(incoming_packets_buffer=buffer_size) as client_endpoint:
|
|
378
|
+
assert client_endpoint.incoming_packets_buffer == buffer_size
|
|
379
|
+
client = client_endpoint.connect(address, client_ctx)
|
|
380
|
+
for i in range(buffer_size + 15):
|
|
381
|
+
await client.send(str(i).encode())
|
|
382
|
+
await trio.sleep(1)
|
|
383
|
+
stats = client.statistics()
|
|
384
|
+
assert stats.incoming_packets_dropped_in_trio == 15
|
|
385
|
+
for i in range(buffer_size):
|
|
386
|
+
assert await client.receive() == str(i).encode()
|
|
387
|
+
await client.send(b"buffer clear now")
|
|
388
|
+
assert await client.receive() == b"buffer clear now"
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
async def test_server_socket_doesnt_crash_on_garbage(
|
|
392
|
+
autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
|
|
393
|
+
) -> None:
|
|
394
|
+
fn = FakeNet()
|
|
395
|
+
fn.enable()
|
|
396
|
+
|
|
397
|
+
from trio._dtls import (
|
|
398
|
+
ContentType,
|
|
399
|
+
HandshakeFragment,
|
|
400
|
+
HandshakeType,
|
|
401
|
+
ProtocolVersion,
|
|
402
|
+
Record,
|
|
403
|
+
encode_handshake_fragment,
|
|
404
|
+
encode_record,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
client_hello = encode_record(
|
|
408
|
+
Record(
|
|
409
|
+
content_type=ContentType.handshake,
|
|
410
|
+
version=ProtocolVersion.DTLS10,
|
|
411
|
+
epoch_seqno=0,
|
|
412
|
+
payload=encode_handshake_fragment(
|
|
413
|
+
HandshakeFragment(
|
|
414
|
+
msg_type=HandshakeType.client_hello,
|
|
415
|
+
msg_len=10,
|
|
416
|
+
msg_seq=0,
|
|
417
|
+
frag_offset=0,
|
|
418
|
+
frag_len=10,
|
|
419
|
+
frag=bytes(10),
|
|
420
|
+
),
|
|
421
|
+
),
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
client_hello_extended = client_hello + b"\x00"
|
|
426
|
+
client_hello_short = client_hello[:-1]
|
|
427
|
+
# cuts off in middle of handshake message header
|
|
428
|
+
client_hello_really_short = client_hello[:14]
|
|
429
|
+
client_hello_corrupt_record_len = bytearray(client_hello)
|
|
430
|
+
client_hello_corrupt_record_len[11] = 0xFF
|
|
431
|
+
|
|
432
|
+
client_hello_fragmented = encode_record(
|
|
433
|
+
Record(
|
|
434
|
+
content_type=ContentType.handshake,
|
|
435
|
+
version=ProtocolVersion.DTLS10,
|
|
436
|
+
epoch_seqno=0,
|
|
437
|
+
payload=encode_handshake_fragment(
|
|
438
|
+
HandshakeFragment(
|
|
439
|
+
msg_type=HandshakeType.client_hello,
|
|
440
|
+
msg_len=20,
|
|
441
|
+
msg_seq=0,
|
|
442
|
+
frag_offset=0,
|
|
443
|
+
frag_len=10,
|
|
444
|
+
frag=bytes(10),
|
|
445
|
+
),
|
|
446
|
+
),
|
|
447
|
+
),
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
client_hello_trailing_data_in_record = encode_record(
|
|
451
|
+
Record(
|
|
452
|
+
content_type=ContentType.handshake,
|
|
453
|
+
version=ProtocolVersion.DTLS10,
|
|
454
|
+
epoch_seqno=0,
|
|
455
|
+
payload=encode_handshake_fragment(
|
|
456
|
+
HandshakeFragment(
|
|
457
|
+
msg_type=HandshakeType.client_hello,
|
|
458
|
+
msg_len=20,
|
|
459
|
+
msg_seq=0,
|
|
460
|
+
frag_offset=0,
|
|
461
|
+
frag_len=10,
|
|
462
|
+
frag=bytes(10),
|
|
463
|
+
),
|
|
464
|
+
)
|
|
465
|
+
+ b"\x00",
|
|
466
|
+
),
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
handshake_empty = encode_record(
|
|
470
|
+
Record(
|
|
471
|
+
content_type=ContentType.handshake,
|
|
472
|
+
version=ProtocolVersion.DTLS10,
|
|
473
|
+
epoch_seqno=0,
|
|
474
|
+
payload=b"",
|
|
475
|
+
),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
client_hello_truncated_in_cookie = encode_record(
|
|
479
|
+
Record(
|
|
480
|
+
content_type=ContentType.handshake,
|
|
481
|
+
version=ProtocolVersion.DTLS10,
|
|
482
|
+
epoch_seqno=0,
|
|
483
|
+
payload=bytes(2 + 32 + 1) + b"\xff",
|
|
484
|
+
),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
488
|
+
with trio.socket.socket(type=trio.socket.SOCK_DGRAM) as sock:
|
|
489
|
+
for bad_packet in [
|
|
490
|
+
b"",
|
|
491
|
+
b"xyz",
|
|
492
|
+
client_hello_extended,
|
|
493
|
+
client_hello_short,
|
|
494
|
+
client_hello_really_short,
|
|
495
|
+
client_hello_corrupt_record_len,
|
|
496
|
+
client_hello_fragmented,
|
|
497
|
+
client_hello_trailing_data_in_record,
|
|
498
|
+
handshake_empty,
|
|
499
|
+
client_hello_truncated_in_cookie,
|
|
500
|
+
]:
|
|
501
|
+
await sock.sendto(bad_packet, address)
|
|
502
|
+
await trio.sleep(1)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
async def test_invalid_cookie_rejected(
|
|
506
|
+
autojump_clock: trio.abc.Clock, server_ctx: SSL.Context, client_ctx: SSL.Context
|
|
507
|
+
) -> None:
|
|
508
|
+
fn = FakeNet()
|
|
509
|
+
fn.enable()
|
|
510
|
+
|
|
511
|
+
from trio._dtls import BadPacket, decode_client_hello_untrusted
|
|
512
|
+
|
|
513
|
+
with trio.CancelScope() as cscope:
|
|
514
|
+
# the first 11 bytes of ClientHello aren't protected by the cookie, so only test
|
|
515
|
+
# corrupting bytes after that.
|
|
516
|
+
offset_to_corrupt = count(11)
|
|
517
|
+
|
|
518
|
+
def route_packet(packet: UDPPacket) -> None:
|
|
519
|
+
try:
|
|
520
|
+
_, cookie, _ = decode_client_hello_untrusted(packet.payload)
|
|
521
|
+
except BadPacket:
|
|
522
|
+
pass
|
|
523
|
+
else:
|
|
524
|
+
if len(cookie) != 0:
|
|
525
|
+
# this is a challenge response packet
|
|
526
|
+
# let's corrupt the next offset so the handshake should fail
|
|
527
|
+
payload = bytearray(packet.payload)
|
|
528
|
+
offset = next(offset_to_corrupt)
|
|
529
|
+
if offset >= len(payload):
|
|
530
|
+
# We've tried all offsets. Clamp offset to the end of the
|
|
531
|
+
# payload, and terminate the test.
|
|
532
|
+
offset = len(payload) - 1
|
|
533
|
+
cscope.cancel()
|
|
534
|
+
payload[offset] ^= 0x01
|
|
535
|
+
packet = attrs.evolve(packet, payload=payload)
|
|
536
|
+
|
|
537
|
+
fn.deliver_packet(packet)
|
|
538
|
+
|
|
539
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO: Fix FakeNet typing
|
|
540
|
+
|
|
541
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
542
|
+
while True:
|
|
543
|
+
with endpoint() as client:
|
|
544
|
+
channel = client.connect(address, client_ctx)
|
|
545
|
+
await channel.do_handshake()
|
|
546
|
+
assert cscope.cancelled_caught
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
async def test_client_cancels_handshake_and_starts_new_one(
|
|
550
|
+
autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
|
|
551
|
+
) -> None:
|
|
552
|
+
# if a client disappears during the handshake, and then starts a new handshake from
|
|
553
|
+
# scratch, then the first handler's channel should fail, and a new handler get
|
|
554
|
+
# started
|
|
555
|
+
fn = FakeNet()
|
|
556
|
+
fn.enable()
|
|
557
|
+
|
|
558
|
+
with endpoint() as server, endpoint() as client:
|
|
559
|
+
await server.socket.bind(("127.0.0.1", 0))
|
|
560
|
+
async with trio.open_nursery() as nursery:
|
|
561
|
+
first_time = True
|
|
562
|
+
|
|
563
|
+
async def handler(channel: DTLSChannel) -> None:
|
|
564
|
+
nonlocal first_time
|
|
565
|
+
if first_time:
|
|
566
|
+
first_time = False
|
|
567
|
+
print("handler: first time, cancelling connect")
|
|
568
|
+
connect_cscope.cancel()
|
|
569
|
+
await trio.sleep(0.5)
|
|
570
|
+
print("handler: handshake should fail now")
|
|
571
|
+
with pytest.raises(trio.BrokenResourceError):
|
|
572
|
+
await channel.do_handshake()
|
|
573
|
+
else:
|
|
574
|
+
print("handler: not first time, sending hello")
|
|
575
|
+
await channel.send(b"hello")
|
|
576
|
+
|
|
577
|
+
await nursery.start(server.serve, server_ctx, handler)
|
|
578
|
+
|
|
579
|
+
print("client: starting first connect")
|
|
580
|
+
with trio.CancelScope() as connect_cscope:
|
|
581
|
+
channel = client.connect(server.socket.getsockname(), client_ctx_fn())
|
|
582
|
+
await channel.do_handshake()
|
|
583
|
+
assert connect_cscope.cancelled_caught
|
|
584
|
+
|
|
585
|
+
print("client: starting second connect")
|
|
586
|
+
channel = client.connect(server.socket.getsockname(), client_ctx_fn())
|
|
587
|
+
assert await channel.receive() == b"hello"
|
|
588
|
+
|
|
589
|
+
# Give handlers a chance to finish
|
|
590
|
+
await trio.sleep(10)
|
|
591
|
+
nursery.cancel_scope.cancel()
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
async def test_swap_client_server(server_ctx: SSL.Context) -> None:
|
|
595
|
+
with endpoint() as a, endpoint() as b:
|
|
596
|
+
await a.socket.bind(("127.0.0.1", 0))
|
|
597
|
+
await b.socket.bind(("127.0.0.1", 0))
|
|
598
|
+
|
|
599
|
+
async def echo_handler(channel: DTLSChannel) -> None:
|
|
600
|
+
async for packet in channel:
|
|
601
|
+
await channel.send(packet)
|
|
602
|
+
|
|
603
|
+
async def crashing_echo_handler(channel: DTLSChannel) -> None:
|
|
604
|
+
with pytest.raises(trio.BrokenResourceError):
|
|
605
|
+
await echo_handler(channel)
|
|
606
|
+
|
|
607
|
+
async with trio.open_nursery() as nursery:
|
|
608
|
+
await nursery.start(a.serve, server_ctx, crashing_echo_handler)
|
|
609
|
+
await nursery.start(b.serve, server_ctx, echo_handler)
|
|
610
|
+
|
|
611
|
+
b_to_a = b.connect(a.socket.getsockname(), client_ctx_fn())
|
|
612
|
+
await b_to_a.send(b"b as client")
|
|
613
|
+
assert await b_to_a.receive() == b"b as client"
|
|
614
|
+
|
|
615
|
+
a_to_b = a.connect(b.socket.getsockname(), client_ctx_fn())
|
|
616
|
+
await a_to_b.do_handshake()
|
|
617
|
+
with pytest.raises(trio.BrokenResourceError):
|
|
618
|
+
await b_to_a.send(b"association broken")
|
|
619
|
+
await a_to_b.send(b"a as client")
|
|
620
|
+
assert await a_to_b.receive() == b"a as client"
|
|
621
|
+
|
|
622
|
+
nursery.cancel_scope.cancel()
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
@slow
|
|
626
|
+
async def test_openssl_retransmit_doesnt_break_stuff(
|
|
627
|
+
server_ctx: SSL.Context, client_ctx: SSL.Context
|
|
628
|
+
) -> None:
|
|
629
|
+
# can't use autojump_clock here, because the point of the test is to wait for
|
|
630
|
+
# openssl's built-in retransmit timer to expire, which is hard-coded to use
|
|
631
|
+
# wall-clock time.
|
|
632
|
+
fn = FakeNet()
|
|
633
|
+
fn.enable()
|
|
634
|
+
|
|
635
|
+
blackholed = True
|
|
636
|
+
|
|
637
|
+
def route_packet(packet: UDPPacket) -> None:
|
|
638
|
+
if blackholed:
|
|
639
|
+
print("dropped packet", packet)
|
|
640
|
+
return
|
|
641
|
+
print("delivered packet", packet)
|
|
642
|
+
# packets.append(
|
|
643
|
+
# scapy.all.IP(
|
|
644
|
+
# src=packet.source.ip.compressed, dst=packet.destination.ip.compressed
|
|
645
|
+
# )
|
|
646
|
+
# / scapy.all.UDP(sport=packet.source.port, dport=packet.destination.port)
|
|
647
|
+
# / packet.payload
|
|
648
|
+
# )
|
|
649
|
+
fn.deliver_packet(packet)
|
|
650
|
+
|
|
651
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
|
|
652
|
+
|
|
653
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (server_endpoint, address):
|
|
654
|
+
with endpoint() as client_endpoint:
|
|
655
|
+
async with trio.open_nursery() as nursery:
|
|
656
|
+
|
|
657
|
+
async def connecter() -> None:
|
|
658
|
+
client = client_endpoint.connect(address, client_ctx)
|
|
659
|
+
await client.do_handshake(initial_retransmit_timeout=1.5)
|
|
660
|
+
await client.send(b"hi")
|
|
661
|
+
assert await client.receive() == b"hi"
|
|
662
|
+
|
|
663
|
+
nursery.start_soon(connecter)
|
|
664
|
+
|
|
665
|
+
# openssl's default timeout is 1 second, so this ensures that it thinks
|
|
666
|
+
# the timeout has expired
|
|
667
|
+
await trio.sleep(1.1)
|
|
668
|
+
# disable blackholing and send a garbage packet to wake up openssl so it
|
|
669
|
+
# notices the timeout has expired
|
|
670
|
+
blackholed = False
|
|
671
|
+
await server_endpoint.socket.sendto(
|
|
672
|
+
b"xxx",
|
|
673
|
+
client_endpoint.socket.getsockname(),
|
|
674
|
+
)
|
|
675
|
+
# now the client task should finish connecting and exit cleanly
|
|
676
|
+
|
|
677
|
+
# scapy.all.wrpcap("/tmp/trace.pcap", packets)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
async def test_initial_retransmit_timeout_configuration(
|
|
681
|
+
autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
|
|
682
|
+
) -> None:
|
|
683
|
+
fn = FakeNet()
|
|
684
|
+
fn.enable()
|
|
685
|
+
|
|
686
|
+
blackholed = True
|
|
687
|
+
|
|
688
|
+
def route_packet(packet: UDPPacket) -> None:
|
|
689
|
+
nonlocal blackholed
|
|
690
|
+
if blackholed:
|
|
691
|
+
blackholed = False
|
|
692
|
+
else:
|
|
693
|
+
fn.deliver_packet(packet)
|
|
694
|
+
|
|
695
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO: add type annotations for FakeNet
|
|
696
|
+
|
|
697
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
698
|
+
for t in [1, 2, 4]:
|
|
699
|
+
with endpoint() as client:
|
|
700
|
+
before = trio.current_time()
|
|
701
|
+
blackholed = True
|
|
702
|
+
channel = client.connect(address, client_ctx_fn())
|
|
703
|
+
await channel.do_handshake(initial_retransmit_timeout=t)
|
|
704
|
+
after = trio.current_time()
|
|
705
|
+
assert after - before == t
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
async def test_explicit_tiny_mtu_is_respected(
|
|
709
|
+
server_ctx: SSL.Context, client_ctx: SSL.Context
|
|
710
|
+
) -> None:
|
|
711
|
+
# ClientHello is ~240 bytes, and it can't be fragmented, so our mtu has to
|
|
712
|
+
# be larger than that. (300 is still smaller than any real network though.)
|
|
713
|
+
MTU = 300
|
|
714
|
+
|
|
715
|
+
fn = FakeNet()
|
|
716
|
+
fn.enable()
|
|
717
|
+
|
|
718
|
+
def route_packet(packet: UDPPacket) -> None:
|
|
719
|
+
print(f"delivering {packet}")
|
|
720
|
+
print(f"payload size: {len(packet.payload)}")
|
|
721
|
+
assert len(packet.payload) <= MTU
|
|
722
|
+
fn.deliver_packet(packet)
|
|
723
|
+
|
|
724
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
|
|
725
|
+
|
|
726
|
+
async with dtls_echo_server(mtu=MTU, server_ctx=server_ctx) as (_server, address):
|
|
727
|
+
with endpoint() as client:
|
|
728
|
+
channel = client.connect(address, client_ctx)
|
|
729
|
+
channel.set_ciphertext_mtu(MTU)
|
|
730
|
+
await channel.do_handshake()
|
|
731
|
+
await channel.send(b"hi")
|
|
732
|
+
assert await channel.receive() == b"hi"
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
@parametrize_ipv6
|
|
736
|
+
async def test_handshake_handles_minimum_network_mtu(
|
|
737
|
+
ipv6: bool,
|
|
738
|
+
autojump_clock: trio.abc.Clock,
|
|
739
|
+
server_ctx: SSL.Context,
|
|
740
|
+
client_ctx: SSL.Context,
|
|
741
|
+
) -> None:
|
|
742
|
+
# Fake network that has the minimum allowable MTU for whatever protocol we're using.
|
|
743
|
+
fn = FakeNet()
|
|
744
|
+
fn.enable()
|
|
745
|
+
|
|
746
|
+
mtu = 1280 - 48 if ipv6 else 576 - 28
|
|
747
|
+
|
|
748
|
+
def route_packet(packet: UDPPacket) -> None:
|
|
749
|
+
if len(packet.payload) > mtu:
|
|
750
|
+
print(f"dropping {packet}")
|
|
751
|
+
else:
|
|
752
|
+
print(f"delivering {packet}")
|
|
753
|
+
fn.deliver_packet(packet)
|
|
754
|
+
|
|
755
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO: add type annotations for FakeNet
|
|
756
|
+
|
|
757
|
+
# See if we can successfully do a handshake -- some of the volleys will get dropped,
|
|
758
|
+
# and the retransmit logic should detect this and back off the MTU to something
|
|
759
|
+
# smaller until it succeeds.
|
|
760
|
+
async with dtls_echo_server(ipv6=ipv6, server_ctx=server_ctx) as (_, address):
|
|
761
|
+
with endpoint(ipv6=ipv6) as client_endpoint:
|
|
762
|
+
client = client_endpoint.connect(address, client_ctx)
|
|
763
|
+
# the handshake mtu backoff shouldn't affect the return value from
|
|
764
|
+
# get_cleartext_mtu, b/c that's under the user's control via
|
|
765
|
+
# set_ciphertext_mtu
|
|
766
|
+
client.set_ciphertext_mtu(9999)
|
|
767
|
+
await client.send(b"xyz")
|
|
768
|
+
assert await client.receive() == b"xyz"
|
|
769
|
+
assert client.get_cleartext_mtu() > 9000 # as vegeta said
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
@pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
|
|
773
|
+
async def test_system_task_cleaned_up_on_gc(client_ctx: SSL.Context) -> None:
|
|
774
|
+
before_tasks = trio.lowlevel.current_statistics().tasks_living
|
|
775
|
+
|
|
776
|
+
# We put this into a sub-function so that everything automatically becomes garbage
|
|
777
|
+
# when the frame exits. For some reason just doing 'del e' wasn't enough on pypy
|
|
778
|
+
# with coverage enabled -- I think we were hitting this bug:
|
|
779
|
+
# https://foss.heptapod.net/pypy/pypy/-/issues/3656
|
|
780
|
+
async def start_and_forget_endpoint() -> int:
|
|
781
|
+
e = endpoint()
|
|
782
|
+
|
|
783
|
+
# This connection/handshake attempt can't succeed. The only purpose is to force
|
|
784
|
+
# the endpoint to set up a receive loop.
|
|
785
|
+
with trio.socket.socket(type=trio.socket.SOCK_DGRAM) as s:
|
|
786
|
+
await s.bind(("127.0.0.1", 0))
|
|
787
|
+
c = e.connect(s.getsockname(), client_ctx)
|
|
788
|
+
async with trio.open_nursery() as nursery:
|
|
789
|
+
nursery.start_soon(c.do_handshake)
|
|
790
|
+
await trio.testing.wait_all_tasks_blocked()
|
|
791
|
+
nursery.cancel_scope.cancel()
|
|
792
|
+
|
|
793
|
+
during_tasks = trio.lowlevel.current_statistics().tasks_living
|
|
794
|
+
return during_tasks
|
|
795
|
+
|
|
796
|
+
with pytest.warns(ResourceWarning): # noqa: PT031
|
|
797
|
+
during_tasks = await start_and_forget_endpoint()
|
|
798
|
+
await trio.testing.wait_all_tasks_blocked()
|
|
799
|
+
gc_collect_harder()
|
|
800
|
+
|
|
801
|
+
await trio.testing.wait_all_tasks_blocked()
|
|
802
|
+
|
|
803
|
+
after_tasks = trio.lowlevel.current_statistics().tasks_living
|
|
804
|
+
assert before_tasks < during_tasks
|
|
805
|
+
assert before_tasks == after_tasks
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
@pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
|
|
809
|
+
async def test_gc_before_system_task_starts() -> None:
|
|
810
|
+
e = endpoint()
|
|
811
|
+
|
|
812
|
+
with pytest.warns(ResourceWarning): # noqa: PT031
|
|
813
|
+
del e
|
|
814
|
+
gc_collect_harder()
|
|
815
|
+
|
|
816
|
+
await trio.testing.wait_all_tasks_blocked()
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
@pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
|
|
820
|
+
async def test_gc_as_packet_received() -> None:
|
|
821
|
+
fn = FakeNet()
|
|
822
|
+
fn.enable()
|
|
823
|
+
|
|
824
|
+
e = endpoint()
|
|
825
|
+
await e.socket.bind(("127.0.0.1", 0))
|
|
826
|
+
e._ensure_receive_loop()
|
|
827
|
+
|
|
828
|
+
await trio.testing.wait_all_tasks_blocked()
|
|
829
|
+
|
|
830
|
+
with trio.socket.socket(type=trio.socket.SOCK_DGRAM) as s:
|
|
831
|
+
await s.sendto(b"xxx", e.socket.getsockname())
|
|
832
|
+
# At this point, the endpoint's receive loop has been marked runnable because it
|
|
833
|
+
# just received a packet; closing the endpoint socket won't interrupt that. But by
|
|
834
|
+
# the time it wakes up to process the packet, the endpoint will be gone.
|
|
835
|
+
with pytest.warns(ResourceWarning): # noqa: PT031
|
|
836
|
+
del e
|
|
837
|
+
gc_collect_harder()
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
@pytest.mark.filterwarnings("always:unclosed DTLS:ResourceWarning")
|
|
841
|
+
def test_gc_after_trio_exits() -> None:
|
|
842
|
+
async def main() -> DTLSEndpoint:
|
|
843
|
+
# We use fakenet just to make sure no real sockets can leak out of the test
|
|
844
|
+
# case - on pypy somehow the socket was outliving the gc_collect_harder call
|
|
845
|
+
# below. Since the test is just making sure DTLSEndpoint.__del__ doesn't explode
|
|
846
|
+
# when called after trio exits, it doesn't need a real socket.
|
|
847
|
+
fn = FakeNet()
|
|
848
|
+
fn.enable()
|
|
849
|
+
return endpoint()
|
|
850
|
+
|
|
851
|
+
e = trio.run(main)
|
|
852
|
+
with pytest.warns(ResourceWarning): # noqa: PT031
|
|
853
|
+
del e
|
|
854
|
+
gc_collect_harder()
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
async def test_already_closed_socket_doesnt_crash() -> None:
|
|
858
|
+
with endpoint() as e:
|
|
859
|
+
# We close the socket before checkpointing, so the socket will already be closed
|
|
860
|
+
# when the system task starts up
|
|
861
|
+
e.socket.close()
|
|
862
|
+
# Now give it a chance to start up, and hopefully not crash
|
|
863
|
+
await trio.testing.wait_all_tasks_blocked()
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
async def test_socket_closed_while_processing_clienthello(
|
|
867
|
+
autojump_clock: trio.abc.Clock, server_ctx: SSL.Context, client_ctx: SSL.Context
|
|
868
|
+
) -> None:
|
|
869
|
+
fn = FakeNet()
|
|
870
|
+
fn.enable()
|
|
871
|
+
|
|
872
|
+
# Check what happens if the socket is discovered to be closed when sending a
|
|
873
|
+
# HelloVerifyRequest, since that has its own sending logic
|
|
874
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (server, address):
|
|
875
|
+
|
|
876
|
+
def route_packet(packet: UDPPacket) -> None:
|
|
877
|
+
fn.deliver_packet(packet)
|
|
878
|
+
server.socket.close()
|
|
879
|
+
|
|
880
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
|
|
881
|
+
|
|
882
|
+
with endpoint() as client_endpoint:
|
|
883
|
+
with trio.move_on_after(10):
|
|
884
|
+
client = client_endpoint.connect(address, client_ctx)
|
|
885
|
+
await client.do_handshake()
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
async def test_association_replaced_while_handshake_running(
|
|
889
|
+
autojump_clock: trio.abc.Clock, server_ctx: SSL.Context
|
|
890
|
+
) -> None:
|
|
891
|
+
fn = FakeNet()
|
|
892
|
+
fn.enable()
|
|
893
|
+
|
|
894
|
+
def route_packet(packet: UDPPacket) -> None:
|
|
895
|
+
pass
|
|
896
|
+
|
|
897
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO: add type annotations for FakeNet
|
|
898
|
+
|
|
899
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
900
|
+
with endpoint() as client_endpoint:
|
|
901
|
+
# TODO: should this have the same exact client_ctx?
|
|
902
|
+
c1 = client_endpoint.connect(address, client_ctx_fn())
|
|
903
|
+
async with trio.open_nursery() as nursery:
|
|
904
|
+
|
|
905
|
+
async def doomed_handshake() -> None:
|
|
906
|
+
with pytest.raises(trio.BrokenResourceError):
|
|
907
|
+
await c1.do_handshake()
|
|
908
|
+
|
|
909
|
+
nursery.start_soon(doomed_handshake)
|
|
910
|
+
|
|
911
|
+
await trio.sleep(10)
|
|
912
|
+
|
|
913
|
+
client_endpoint.connect(address, client_ctx_fn())
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
async def test_association_replaced_before_handshake_starts(
|
|
917
|
+
server_ctx: SSL.Context,
|
|
918
|
+
) -> None:
|
|
919
|
+
fn = FakeNet()
|
|
920
|
+
fn.enable()
|
|
921
|
+
|
|
922
|
+
# This test shouldn't send any packets
|
|
923
|
+
def route_packet(packet: UDPPacket) -> NoReturn: # pragma: no cover
|
|
924
|
+
raise AssertionError()
|
|
925
|
+
|
|
926
|
+
fn.route_packet = route_packet # type: ignore[assignment] # TODO add type annotations for FakeNet
|
|
927
|
+
|
|
928
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
929
|
+
with endpoint() as client_endpoint:
|
|
930
|
+
# TODO: should this use the same client_ctx?
|
|
931
|
+
c1 = client_endpoint.connect(address, client_ctx_fn())
|
|
932
|
+
client_endpoint.connect(address, client_ctx_fn())
|
|
933
|
+
with pytest.raises(trio.BrokenResourceError):
|
|
934
|
+
await c1.do_handshake()
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
async def test_send_to_closed_local_port(server_ctx: SSL.Context) -> None:
|
|
938
|
+
# On Windows, sending a UDP packet to a closed local port can cause a weird
|
|
939
|
+
# ECONNRESET error later, inside the receive task. Make sure we're handling it
|
|
940
|
+
# properly.
|
|
941
|
+
async with dtls_echo_server(server_ctx=server_ctx) as (_, address):
|
|
942
|
+
with endpoint() as client_endpoint:
|
|
943
|
+
async with trio.open_nursery() as nursery:
|
|
944
|
+
for i in range(1, 10):
|
|
945
|
+
channel = client_endpoint.connect(("127.0.0.1", i), client_ctx_fn())
|
|
946
|
+
nursery.start_soon(channel.do_handshake)
|
|
947
|
+
channel = client_endpoint.connect(address, client_ctx_fn())
|
|
948
|
+
await channel.send(b"xxx")
|
|
949
|
+
assert await channel.receive() == b"xxx"
|
|
950
|
+
nursery.cancel_scope.cancel()
|