Complete Phase 12 dual-stack networking
Add IPv6-aware Wi-Fi state, HTTPS/SSH listeners, mDNS service reconciliation, and browser Wi-Fi administration. Include a guarded build-local fix for mDNS 1.12.0 membership handling, focused regression suites, and Phase 12 acceptance documentation.
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
"""Dependency-free contract tests against the production parser, not a model."""
|
||||
import ctypes as C
|
||||
import json
|
||||
import ipaddress
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -33,6 +35,40 @@ def main():
|
||||
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror",
|
||||
"-shared", "-fPIC", "-I", str(ROOT / "src"),
|
||||
str(ROOT / "src/web_auth_parse.c"), "-o", str(library)], check=True)
|
||||
# Optional audit uses installed target conversion code, not a mocked
|
||||
# inet_pton that merely returns success or delegates IPv6 to host libc.
|
||||
sdk = os.environ.get("WEB_AUTH_LWIP_SOURCE")
|
||||
if sdk:
|
||||
source = Path(sdk).read_text()
|
||||
start = source.index("int\nip6addr_aton(")
|
||||
end = source.index("\n/**", start)
|
||||
shim = Path(temporary) / "lwip.c"
|
||||
shim.write_text('''#include <arpa/inet.h>
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
typedef uint32_t u32_t;
|
||||
typedef struct { uint32_t addr[4]; } ip6_addr_t;
|
||||
#define LWIP_IPV4 0
|
||||
#define LWIP_IPV6_SCOPES 0
|
||||
#define lwip_htonl htonl
|
||||
#define lwip_isxdigit isxdigit
|
||||
#define lwip_isdigit isdigit
|
||||
#define lwip_islower islower
|
||||
#define ip6_addr_clear_zone(a) ((void)(a))
|
||||
''' + source[start:end] + '''
|
||||
int audit_inet_pton(int af, const char *src, void *dst) {
|
||||
ip6_addr_t addr;
|
||||
if (af != AF_INET6) return -1;
|
||||
int result = ip6addr_aton(src, &addr);
|
||||
if (result) memcpy(dst, addr.addr, sizeof(addr.addr));
|
||||
return result;
|
||||
}
|
||||
''')
|
||||
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror",
|
||||
"-shared", "-fPIC", "-Dinet_pton=audit_inet_pton",
|
||||
"-I", str(ROOT / "src"), str(ROOT / "src/web_auth_parse.c"),
|
||||
str(shim), "-o", str(library)], check=True)
|
||||
api = C.CDLL(str(library))
|
||||
api.web_auth_parse_origin.argtypes = [C.c_void_p, C.c_size_t, C.c_void_p, C.c_size_t, C.c_void_p]
|
||||
api.web_auth_parse_cookie.argtypes = [C.c_void_p, C.c_size_t, C.c_char_p, C.c_void_p]
|
||||
@@ -52,7 +88,7 @@ def main():
|
||||
if not result and raw != bytes(len(raw)):
|
||||
failures.append(f"{kind}: {label}: failure did not wipe every output byte")
|
||||
if result and expected is not None and extract(output) != expected:
|
||||
failures.append(f"{kind}: {label}: incorrect decoded/canonical output")
|
||||
failures.append(f"{kind}: {label}: decoded/canonical {extract(output)!r}, expected {expected!r}")
|
||||
|
||||
origins = [(b"EXAMPLE.Com", b"https://example.com", b"https://example.com"),
|
||||
(b"a-b.local", b"https://A-B.LOCAL", b"https://a-b.local"),
|
||||
@@ -65,8 +101,7 @@ def main():
|
||||
b"https://" + host.lower()))
|
||||
bad_hosts = [b"", b" ", b"example.com ", b" example.com", b"a..b", b".a", b"a.",
|
||||
b"-a", b"a-", b"a_b", b"a/b", b"a?b", b"a#b", b"u@a", b"a,b",
|
||||
b"a\\b", b"a\tb", b"a\r\nb", b"a\0b", b"caf\xc3\xa9", b"[::1]", b"::1",
|
||||
b"[::1]:443", b"a:80", b"a:444", b"a:", b"a:0443", b"a:+443",
|
||||
b"a\\b", b"a\tb", b"a\r\nb", b"a\0b", b"caf\xc3\xa9", b"::1", b"a:80", b"a:444", b"a:", b"a:0443", b"a:+443",
|
||||
b"a:443:443", b"a" * 64 + b".com", b"a" * 130]
|
||||
origins += [(h, b"https://" + h, None) for h in bad_hosts]
|
||||
origins += [(b"example.com", o, None) for o in
|
||||
@@ -77,6 +112,39 @@ def main():
|
||||
b"https://example.com\0", b"https://example.com\r\n", b"https://[::1]",
|
||||
b"https://example.com https://example.com", b"https:///example.com")]
|
||||
origins += [(None, b"https://example.com", None)]
|
||||
literals = ["::", "::1", "2001:DB8::ABCD", "2001:0:0:1:0:0:0:1",
|
||||
"1:0:0:2:0:0:3:4", "1:2:3:4:5:6:0:8", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
||||
"::ffff:192.0.2.1", "::192.0.2.1", "2001:db8:1:2:3:4:192.0.2.1"]
|
||||
for literal in literals:
|
||||
address = ipaddress.IPv6Address(literal)
|
||||
normalized = address.compressed
|
||||
if address.ipv4_mapped:
|
||||
value = int(address.ipv4_mapped)
|
||||
normalized = f"::ffff:{value >> 16:x}:{value & 65535:x}"
|
||||
canonical = b"[" + normalized.encode() + b"]"
|
||||
for h in (literal.encode(), address.exploded.upper().encode()):
|
||||
for hp in (b"", b":443"):
|
||||
for op in (b"", b":443"):
|
||||
origins.append((b"[" + h + b"]" + hp, b"https://" + canonical + op,
|
||||
b"https://" + canonical))
|
||||
invalid = [b"[]", b"[1]", b"[1:2:3:4:5:6:7]", b"[1:2:3:4:5:6:7:8:9]",
|
||||
b"[1:2:3:4:5:6:7:8::]", b"[:::]", b"[1::2::3]", b"[:1]", b"[1:]",
|
||||
b"[12345::]", b"[gg::]", b"[::1", b"::1]", b"[[::1]]",
|
||||
b"[::ffff:192.00.2.1]", b"[::ffff:256.0.2.1]", b"[::ffff:192.2.1]",
|
||||
b"[::ffff:0xc0.0.2.1]", b"[::ffff:192.0.2.1:1]",
|
||||
b"[fe80::1%eth0]", b"[fe80::1%25eth0]", b"user@[::1]",
|
||||
b"[::1]:80", b"[::1]:0443", b"[::1]:+443", b"[::1]:443:443",
|
||||
b"[::1]:", b"[::1]/", b"[::1]?x", b"[::1]#x", b"[::1]@host"]
|
||||
for c in list(range(33)) + [127, 128, 255]:
|
||||
invalid.extend((b"[::1" + bytes([c]) + b"]", b"[::1]" + bytes([c])))
|
||||
for h in invalid:
|
||||
origins.extend(((h, b"https://" + h, None),
|
||||
(h, b"https://[::1]", None),
|
||||
(b"[::1]", b"https://" + h, None)))
|
||||
for h, o in ((b"[::1]", b"[::2]"), (b"[::1]", b"localhost"),
|
||||
(b"[::ffff:192.0.2.1]", b"192.0.2.1"),
|
||||
(b"[::192.0.2.1]", b"[::ffff:192.0.2.1]")):
|
||||
origins.extend(((h, b"https://" + o, None), (o, b"https://" + h, None)))
|
||||
# 128 canonical bytes fit with the terminator; 129 do not.
|
||||
for n in (56, 57):
|
||||
host = b"a" * 63 + b"." + b"b" * n
|
||||
|
||||
Reference in New Issue
Block a user