AtlatestRepositorysigil-http

sigil-http / tree / test / integrationtimeout-servers.py

1#!/usr/bin/env python3
2"""Servers for the live timeout controls.
3
4Two healthy servers, deliberately boring: they answer correctly and promptly.
5Their whole job is to prove that the new bounds do NOT break a request that
6should work, which no stall rig can show.
7
8Routes:
9 / 200, {"status":"ok"}
10 /echo-length 200, the request body's byte length as text
11 /bytes 200, exactly 65536 bytes of application/octet-stream
13And one DRIP peer, which is the control that a silent stall rig cannot
14provide. See its docstring: it is the difference between a bound that is
15per-read and one that is per-handshake, and a rig made only of peers that
16send NOTHING cannot tell those apart.
18Usage: timeout-servers.py <http-port> <tls-port> <certfile> <keyfile> <drip-port>
19"""
20import http.server
21import socket
22import ssl
23import sys
24import threading
25import time
27BYTES_LEN = 65536
29# One byte per interval. Long enough that a per-read bound of a comparable
30# size resets on every byte, which is exactly the defect being tested for.
31DRIP_INTERVAL_S = 0.6
33# A well-formed TLS record header: handshake (0x16), TLS 1.2 (0x0303), then a
34# declared body length of 0x4000 = 16384 bytes. The client will therefore sit
35# in mbedtls_ssl_fetch_input waiting for 16384 bytes that arrive one at a
36# time. The header must be VALID or the client rejects it immediately and the
37# result is "handshake-failed" rather than a timeout, which would test
38# nothing.
39TLS_RECORD_HEADER = bytes([0x16, 0x03, 0x03, 0x40, 0x00])
42def drip_server(port):
43 """Accept, send a valid record header, then trickle body bytes forever.
45 A peer that goes silent and a peer that DRIPS are different failure
46 geometries, and a handshake bound can be correct against the first while
47 being useless against the second: mbedtls_ssl_handshake does not return
48 between steps and passes the full configured read timeout to every
49 partial read, so each dribbled byte restarts the clock. Measured against
50 an earlier build, a 1000 ms bound took 13.06 s to fire here, and never
51 fired at all while the drip continued.
52 """
53 srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
54 srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
55 srv.bind(("127.0.0.1", port))
56 srv.listen(16)
58 def handle(conn):
59 try:
60 conn.sendall(TLS_RECORD_HEADER)
61 while True:
62 time.sleep(DRIP_INTERVAL_S)
63 conn.sendall(b"\x00")
64 except OSError:
65 pass
66 finally:
67 try:
68 conn.close()
69 except OSError:
70 pass
72 while True:
73 client, _ = srv.accept()
74 threading.Thread(target=handle, args=(client,), daemon=True).start()
77class Handler(http.server.BaseHTTPRequestHandler):
78 protocol_version = "HTTP/1.1"
80 def _send(self, body, ctype="text/plain"):
81 self.send_response(200)
82 self.send_header("Content-Type", ctype)
83 self.send_header("Content-Length", str(len(body)))
84 self.end_headers()
85 self.wfile.write(body)
87 def do_GET(self):
88 if self.path == "/bytes":
89 self._send(bytes(i % 256 for i in range(BYTES_LEN)),
90 "application/octet-stream")
91 else:
92 self._send(b'{"status":"ok"}', "application/json")
94 def do_POST(self):
95 length = int(self.headers.get("Content-Length", "0"))
96 read = 0
97 while read < length:
98 chunk = self.rfile.read(min(65536, length - read))
99 if not chunk:
100 break
101 read += len(chunk)
102 if self.path == "/echo-length":
103 self._send(str(read).encode())
104 else:
105 self._send(b'{"status":"ok"}', "application/json")
107 def log_message(self, *args):
108 pass
111def serve(port, certfile=None, keyfile=None):
112 srv = http.server.ThreadingHTTPServer(("127.0.0.1", port), Handler)
113 if certfile:
114 ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
115 ctx.load_cert_chain(certfile, keyfile)
116 srv.socket = ctx.wrap_socket(srv.socket, server_side=True)
117 srv.serve_forever()
120def main():
121 http_port = int(sys.argv[1])
122 tls_port = int(sys.argv[2])
123 certfile = sys.argv[3]
124 keyfile = sys.argv[4]
125 drip_port = int(sys.argv[5])
127 threading.Thread(target=serve, args=(http_port,), daemon=True).start()
128 threading.Thread(target=serve, args=(tls_port, certfile, keyfile),
129 daemon=True).start()
130 threading.Thread(target=drip_server, args=(drip_port,), daemon=True).start()
132 # The runner waits for this line before starting the tests.
133 print("SERVERS READY http=%d tls=%d drip=%d"
134 % (http_port, tls_port, drip_port), flush=True)
135 threading.Event().wait()
138if __name__ == "__main__":
139 main()