mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 17:11:15 -07:00
forgectrl.tls-records: a window another client connected in is measured again
The test judges each connection by the kernel's TLS counters and the crypto engine's interrupt, and both are the machine's, not the connection's. On image 20260928175056's campaign the test before it restarted forgectrl, the operator's panel (six HTTPS connections from the bench PC) reconnected in the same second as aes-1.3#1, and the kernel counted 7 sessions each way where the test wanted exactly 1; the connection itself chose AES-128-GCM, carried the page byte for byte, and counted no decrypt error. _fetch now also reads the machine's accepted TCP connections (PassiveOpens, shared by both families); a case whose window saw any connection but its own is measured again, up to 30 times after a pause drawn from 0.1 to 0.9 s (so no steady poller, such as a page polling this suite once a second, stays in step with it), and fails if every window was contested. The judgments are unchanged: exactly one session each way for a cipher the kernel seals, none for CBC, the engine's interrupt counting the AES records and not the ChaCha20 ones. Proof: pyflakes clean. On the bench reference, image 20260928175056, with an openssl client connecting to :443 every 0.25 s for the first 6 s of the run and the drill polling this suite once a second: the image's test fails (desktop-1.2 counted 2 sessions each way), the fixed test passes with contested windows measured again (at most 4 attempts a case), and passes again without the extra clients (at most 4). Acceptance: the change is forgectrl.tls-records itself; tlsrec.py holds no other test, so no other fingerprint moves.
This commit is contained in:
@@ -23,6 +23,7 @@ of every test there.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -51,6 +52,14 @@ def _tls_stat():
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _passive_opens():
|
||||||
|
"""The TCP connections the machine has accepted, on every port and both
|
||||||
|
families (the TCP counters are shared)."""
|
||||||
|
with open("/proc/net/snmp") as f:
|
||||||
|
rows = [line.split() for line in f if line.startswith("Tcp:")]
|
||||||
|
return int(rows[1][rows[0].index("PassiveOpens")])
|
||||||
|
|
||||||
|
|
||||||
def _engine_irqs():
|
def _engine_irqs():
|
||||||
with open("/proc/interrupts") as f:
|
with open("/proc/interrupts") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
@@ -80,16 +89,17 @@ def _driver(name):
|
|||||||
def _fetch(version, offer, work):
|
def _fetch(version, offer, work):
|
||||||
"""GET the page over HTTPS on loopback with the given offer. Returns
|
"""GET the page over HTTPS on loopback with the given offer. Returns
|
||||||
the suite, the status line, the body, the kernel counters' change,
|
the suite, the status line, the body, the kernel counters' change,
|
||||||
and the crypto engine's interrupts during the connection."""
|
the crypto engine's interrupts during the connection, and how many
|
||||||
|
other connections the machine accepted meanwhile."""
|
||||||
req = os.path.join(work, "req")
|
req = os.path.join(work, "req")
|
||||||
with open(req, "wb") as f:
|
with open(req, "wb") as f:
|
||||||
f.write(b"GET " + PAGE.encode() + b" HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n")
|
f.write(b"GET " + PAGE.encode() + b" HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n")
|
||||||
opt = ["-tls1_3", "-ciphersuites", offer] if version == "1.3" else ["-tls1_2", "-cipher", offer]
|
opt = ["-tls1_3", "-ciphersuites", offer] if version == "1.3" else ["-tls1_2", "-cipher", offer]
|
||||||
s0, i0 = _tls_stat(), _engine_irqs()
|
s0, i0, p0 = _tls_stat(), _engine_irqs(), _passive_opens()
|
||||||
with open(req, "rb") as stdin:
|
with open(req, "rb") as stdin:
|
||||||
r = subprocess.run([OPENSSL, "s_client", "-connect", "127.0.0.1:443", "-brief", "-ign_eof"] + opt,
|
r = subprocess.run([OPENSSL, "s_client", "-connect", "127.0.0.1:443", "-brief", "-ign_eof"] + opt,
|
||||||
stdin=stdin, capture_output=True, timeout=60)
|
stdin=stdin, capture_output=True, timeout=60)
|
||||||
s1, i1 = _tls_stat(), _engine_irqs()
|
s1, i1, p1 = _tls_stat(), _engine_irqs(), _passive_opens()
|
||||||
info = {}
|
info = {}
|
||||||
for line in r.stderr.decode("utf-8", "replace").splitlines():
|
for line in r.stderr.decode("utf-8", "replace").splitlines():
|
||||||
k, _, v = line.partition(":")
|
k, _, v = line.partition(":")
|
||||||
@@ -97,7 +107,7 @@ def _fetch(version, offer, work):
|
|||||||
head, _, body = r.stdout.partition(b"\r\n\r\n")
|
head, _, body = r.stdout.partition(b"\r\n\r\n")
|
||||||
delta = {k: s1.get(k, 0) - s0.get(k, 0) for k in ("TlsTxSw", "TlsRxSw", "TlsDecryptError")}
|
delta = {k: s1.get(k, 0) - s0.get(k, 0) for k in ("TlsTxSw", "TlsRxSw", "TlsDecryptError")}
|
||||||
return (info.get("Ciphersuite"), head.split(b"\r\n")[0].decode("latin-1"), body, delta,
|
return (info.get("Ciphersuite"), head.split(b"\r\n")[0].decode("latin-1"), body, delta,
|
||||||
(i1 or 0) - (i0 or 0))
|
(i1 or 0) - (i0 or 0), p1 - p0 - 1)
|
||||||
|
|
||||||
|
|
||||||
@test("forgectrl.tls-records", title="HTTPS: ChaCha20 chosen, records sealed in the kernel",
|
@test("forgectrl.tls-records", title="HTTPS: ChaCha20 chosen, records sealed in the kernel",
|
||||||
@@ -116,7 +126,9 @@ def _fetch(version, offer, work):
|
|||||||
"offering a CBC suite alone still connects and stays in GnuTLS, which is the control "
|
"offering a CBC suite alone still connects and stays in GnuTLS, which is the control "
|
||||||
"for the kernel's counters; the ChaCha20 runs are the control for the engine's "
|
"for the kernel's counters; the ChaCha20 runs are the control for the engine's "
|
||||||
"interrupt. Every copy of the panel page must be the plain-HTTP page byte for byte, "
|
"interrupt. Every copy of the panel page must be the plain-HTTP page byte for byte, "
|
||||||
"and the kernel must count no decrypt error. Nothing on the machine changes.")
|
"and the kernel must count no decrypt error. The counters and the interrupt are the "
|
||||||
|
"machine's, so a connection during which another client connected (a panel "
|
||||||
|
"reconnecting after a restart) is measured again. Nothing on the machine changes.")
|
||||||
def tls_records(ctx):
|
def tls_records(ctx):
|
||||||
ev = ctx.evidence
|
ev = ctx.evidence
|
||||||
|
|
||||||
@@ -152,9 +164,19 @@ def tls_records(ctx):
|
|||||||
True)]
|
True)]
|
||||||
cases.append(("cbc-1.2", "1.2", "ECDHE-ECDSA-AES128-SHA", "ECDHE-ECDSA-AES128-SHA", False))
|
cases.append(("cbc-1.2", "1.2", "ECDHE-ECDSA-AES128-SHA", "ECDHE-ECDSA-AES128-SHA", False))
|
||||||
for label, version, offer, want, kernel in cases:
|
for label, version, offer, want, kernel in cases:
|
||||||
suite, status, body, delta, irqs = _fetch(version, offer, work)
|
# The kernel's counters and the engine's interrupt are the machine's, not the
|
||||||
|
# connection's: a window in which another client connected (a panel reconnecting
|
||||||
|
# after a restart, a page polling this suite once a second) says nothing about this
|
||||||
|
# one, and is measured again, after a pause that no steady poller stays in step with.
|
||||||
|
for attempt in range(1, 31):
|
||||||
|
suite, status, body, delta, irqs, others = _fetch(version, offer, work)
|
||||||
|
if others <= 0:
|
||||||
|
break
|
||||||
|
ctx.log("%s: %d other connection(s) during the window; measured again", label, others)
|
||||||
|
ctx.sleep(random.uniform(0.1, 0.9))
|
||||||
|
ctx.check(others <= 0, "%s: another client connected during each of %d windows", label, attempt)
|
||||||
run = {"offer": offer, "suite": suite, "status": status, "bytes": len(body),
|
run = {"offer": offer, "suite": suite, "status": status, "bytes": len(body),
|
||||||
"same_as_http": body == plain, "tls_stat": delta, "engine_irqs": irqs}
|
"same_as_http": body == plain, "tls_stat": delta, "engine_irqs": irqs, "attempts": attempt}
|
||||||
runs[label] = run
|
runs[label] = run
|
||||||
ctx.log("%s: offer %s -> %s, %s, %d bytes, same %s, tls_stat %s, %s +%d",
|
ctx.log("%s: offer %s -> %s, %s, %d bytes, same %s, tls_stat %s, %s +%d",
|
||||||
label, offer, suite, status, len(body), body == plain, delta, ENGINE_IRQ, irqs)
|
label, offer, suite, status, len(body), body == plain, delta, ENGINE_IRQ, irqs)
|
||||||
|
|||||||
Reference in New Issue
Block a user