hololake-system-architecture/product-source/hololake-platform/guanghu-os/scripts/physical-native-icmp-peer.py

452 lines
18 KiB
Python
Executable file

#!/usr/bin/env python3
import argparse
import os
import pathlib
import socket
import struct
import time
LOGIN_MAGIC = b"HLDP-GHOS-LOGIN!"
COMMIT_MAGIC = b"HLDP-CODE-COMMIT"
BRANCH_MAGIC = b"HLDP-BRANCH-MOVE"
RECOVERY_MAGIC = b"HLDP-RECOVER-OS!"
NATIVE_ACK_MAGIC = b"HLDP-NATIVE-ACK!"
def checksum(payload: bytes) -> int:
if len(payload) % 2:
payload += b"\0"
words = struct.unpack(f"!{len(payload) // 2}H", payload)
total = sum(words)
while total >> 16:
total = (total & 0xFFFF) + (total >> 16)
return (~total) & 0xFFFF
def request(sequence: int, magic: bytes) -> bytes:
payload = b"\0" * 8 + magic + magic
packet = struct.pack("!BBHHH", 8, 0, 0, 0x4748, sequence) + payload
return packet[:2] + struct.pack("!H", checksum(packet)) + packet[4:]
def reply_sequence_index(packet: bytes) -> int:
if packet and packet[0] >> 4 == 4:
header_length = (packet[0] & 0x0F) * 4
packet = packet[header_length:]
if len(packet) < 8:
raise RuntimeError("ICMP reply is too short")
return int.from_bytes(packet[6:8], "big")
def verify_reply(packet: bytes, magic: bytes) -> int:
if packet and packet[0] >> 4 == 4:
header_length = (packet[0] & 0x0F) * 4
packet = packet[header_length:]
if len(packet) < 48 or packet[0] != 0 or packet[1] != 0:
raise RuntimeError("unexpected ICMP reply shape")
if packet[16:32] != magic:
raise RuntimeError("ICMP reply does not carry the requested HLDP magic")
if packet[32:48] != NATIVE_ACK_MAGIC:
raise RuntimeError("ICMP reply does not carry the native ACK marker")
if checksum(packet) != 0:
raise RuntimeError("ICMP reply checksum failed")
return int.from_bytes(packet[6:8], "big")
def verify_native_request(packet: bytes, pipeline: list[bytes]) -> tuple[int, str]:
if not packet or packet[0] >> 4 != 4:
raise RuntimeError("native request is missing its IPv4 header")
header_length = (packet[0] & 0x0F) * 4
source = socket.inet_ntoa(packet[12:16])
icmp = packet[header_length:]
if len(icmp) < 48 or icmp[0] != 8 or icmp[1] != 0:
raise RuntimeError("unexpected native ICMP request shape")
if checksum(icmp) != 0:
raise RuntimeError("native ICMP request checksum failed")
sequence = int.from_bytes(icmp[6:8], "big")
if not 1 <= sequence <= len(pipeline):
raise RuntimeError("native ICMP sequence is outside the pipeline")
magic = pipeline[sequence - 1]
if icmp[16:32] != magic or icmp[32:48] != magic:
raise RuntimeError("native ICMP request magic mismatch")
return sequence, source
def native_ack_reply(
packet: bytes,
response_capability: bytes = NATIVE_ACK_MAGIC,
) -> bytes:
if len(response_capability) != 16:
raise ValueError("response capability must be exactly 16 bytes")
header_length = (packet[0] & 0x0F) * 4
icmp = bytearray(packet[header_length:])
icmp[0] = 0
icmp[2:4] = b"\0\0"
icmp[32:48] = response_capability
icmp[2:4] = struct.pack("!H", checksum(bytes(icmp)))
return bytes(icmp)
def exchange(
peer: socket.socket,
target: tuple[str, int],
sequence: int,
magic: bytes,
deadline: float,
retry_interval: float,
) -> None:
while time.monotonic() < deadline:
peer.sendto(request(sequence, magic), target)
attempt_started = time.monotonic()
try:
packet, _ = peer.recvfrom(4096)
except TimeoutError:
time.sleep(max(0.0, retry_interval - (time.monotonic() - attempt_started)))
continue
try:
verify_reply(packet, magic)
except RuntimeError:
time.sleep(max(0.0, retry_interval - (time.monotonic() - attempt_started)))
continue
return
raise TimeoutError(f"no verified reply for sequence {sequence}")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--target")
parser.add_argument("--receipt", required=True)
parser.add_argument("--timeout", type=float, default=90.0)
parser.add_argument("--retry-interval", type=float, default=1.0)
parser.add_argument("--raw-socket", action="store_true")
parser.add_argument("--resident", action="store_true")
parser.add_argument("--login-only", action="store_true")
parser.add_argument("--resume-after-login", action="store_true")
parser.add_argument("--resume-after-commit", action="store_true")
parser.add_argument("--resume-after-branch", action="store_true")
parser.add_argument("--resume-resident-count", type=int)
parser.add_argument("--resident-pipeline", action="store_true")
parser.add_argument("--native-relay", action="store_true")
parser.add_argument("--final-resident-relay", action="store_true")
parser.add_argument("--recovery-token-file")
parser.add_argument("--recovery-request-file")
parser.add_argument("--allowed-source")
parser.add_argument("--native-ack-delay", type=float, default=0.25)
parser.add_argument("--native-ack-repetitions", type=int, default=4)
parser.add_argument("--native-ack-interval", type=float, default=0.1)
args = parser.parse_args()
if args.retry_interval < 1.0:
raise SystemExit("--retry-interval must be at least 1 second")
if args.resume_resident_count is not None and not (
0 <= args.resume_resident_count <= 10
):
raise SystemExit("--resume-resident-count must be between 0 and 10")
if not 0.0 <= args.native_ack_delay <= 2.0:
raise SystemExit("--native-ack-delay must be between 0 and 2 seconds")
if not 1 <= args.native_ack_repetitions <= 10:
raise SystemExit("--native-ack-repetitions must be between 1 and 10")
if not 0.0 <= args.native_ack_interval <= 1.0:
raise SystemExit("--native-ack-interval must be between 0 and 1 second")
if sum(
(
args.resident,
args.login_only,
args.resume_after_login,
args.resume_after_commit,
args.resume_after_branch,
args.resume_resident_count is not None,
args.resident_pipeline,
args.native_relay,
args.final_resident_relay,
)
) > 1:
raise SystemExit(
"--resident, --login-only, --resume-after-login, and "
"--resume-after-commit, --resume-after-branch, and "
"--resume-resident-count, --resident-pipeline, --native-relay, "
"and --final-resident-relay "
"are mutually exclusive"
)
socket_type = socket.SOCK_RAW if args.raw_socket else socket.SOCK_DGRAM
peer = socket.socket(socket.AF_INET, socket_type, socket.IPPROTO_ICMP)
deadline = time.monotonic() + args.timeout
if args.native_relay or args.final_resident_relay:
if not args.raw_socket:
raise SystemExit("native relay modes require --raw-socket")
if not args.allowed_source:
raise SystemExit("native relay modes require --allowed-source")
recovery_capability = None
if args.final_resident_relay:
if not args.recovery_token_file or not args.recovery_request_file:
raise SystemExit(
"--final-resident-relay requires --recovery-token-file "
"and --recovery-request-file"
)
token_hex = (
pathlib.Path(args.recovery_token_file)
.read_text(encoding="ascii")
.strip()
)
recovery_capability = bytes.fromhex(token_hex)
if len(recovery_capability) != 16:
raise SystemExit("recovery token must be exactly 16 bytes")
allowed_source = socket.gethostbyname(args.allowed_source)
pipeline = (
[
LOGIN_MAGIC,
LOGIN_MAGIC,
LOGIN_MAGIC,
COMMIT_MAGIC,
BRANCH_MAGIC,
LOGIN_MAGIC,
]
if args.final_resident_relay
else [
LOGIN_MAGIC,
LOGIN_MAGIC,
LOGIN_MAGIC,
COMMIT_MAGIC,
BRANCH_MAGIC,
*([LOGIN_MAGIC] * 10),
RECOVERY_MAGIC,
]
)
peer.settimeout(0.5)
acknowledged: set[int] = set()
sources: set[str] = set()
completed_at: float | None = None
recovery_capability_sent = False
while time.monotonic() < deadline and (
completed_at is None or time.monotonic() < completed_at + 5.0
):
try:
packet, address = peer.recvfrom(4096)
sequence, source = verify_native_request(packet, pipeline)
except (TimeoutError, RuntimeError, IndexError):
continue
if source != allowed_source:
continue
# The public-cloud NAT path is established by the host kernel's
# ordinary echo reply. Delay the authenticated ACK long enough for
# the native guest to discard that reply and repost its RX buffer.
# Repeat the authenticated frame because the physical JD path can
# lose a single raw-socket reply after NAT has been established.
time.sleep(args.native_ack_delay)
response_capability = NATIVE_ACK_MAGIC
if (
args.final_resident_relay
and sequence == 6
and os.path.isfile(args.recovery_request_file)
):
response_capability = recovery_capability
recovery_capability_sent = True
reply = native_ack_reply(packet, response_capability)
for repetition in range(args.native_ack_repetitions):
peer.sendto(reply, address)
if repetition + 1 < args.native_ack_repetitions:
time.sleep(args.native_ack_interval)
print(
f"native_ack sequence={sequence} source={source} "
f"repetitions={args.native_ack_repetitions}",
flush=True,
)
acknowledged.add(sequence)
sources.add(source)
if args.final_resident_relay:
ready = all(sequence_id in acknowledged for sequence_id in range(1, 7))
receipt = pathlib.Path(args.receipt)
receipt.write_text(
"schema: guanghu.physical-native-final-resident-relay/v1\n"
f"status: {'RECOVERY_CAPABILITY_SENT' if recovery_capability_sent else 'READY_NATIVE_RESIDENT' if ready else 'STARTING'}\n"
"handshake_direction: NATIVE_INITIATED_OUTBOUND_ICMP\n"
"native_ack_marker: HLDP-NATIVE-ACK!\n"
"relay_cannot_select_command: true\n"
"recovery_requires_root_owned_request_file: true\n"
f"recovery_capability_sent: {str(recovery_capability_sent).lower()}\n"
f"acknowledged_sequences: {','.join(str(item) for item in sorted(acknowledged))}\n"
f"observed_nat_sources: {','.join(sorted(sources))}\n",
encoding="utf-8",
)
if ready and recovery_capability_sent and completed_at is None:
completed_at = time.monotonic()
elif len(acknowledged) == len(pipeline) and completed_at is None:
completed_at = time.monotonic()
if args.final_resident_relay:
if not all(sequence_id in acknowledged for sequence_id in range(1, 7)):
raise TimeoutError(
"native final-resident relay did not reach ready state; "
"acknowledged sequences: "
+ ",".join(str(item) for item in sorted(acknowledged))
)
if not recovery_capability_sent:
raise TimeoutError(
"native final-resident relay remained ready but no "
"authorized recovery request was observed"
)
print(pathlib.Path(args.receipt).read_text(encoding="utf-8"), end="")
return
if len(acknowledged) != len(pipeline):
raise TimeoutError(
"native relay pipeline incomplete; acknowledged sequences: "
+ ",".join(str(item) for item in sorted(acknowledged))
)
receipt = pathlib.Path(args.receipt)
receipt.write_text(
"schema: guanghu.physical-native-icmp-relay/v1\n"
"status: PASS_100\n"
"handshake_direction: NATIVE_INITIATED_OUTBOUND_ICMP\n"
"native_ack_marker: HLDP-NATIVE-ACK!\n"
f"native_ack_delay_seconds: {args.native_ack_delay:g}\n"
f"native_ack_repetitions: {args.native_ack_repetitions}\n"
f"native_ack_interval_seconds: {args.native_ack_interval:g}\n"
"acknowledged_sequences: 1-16\n"
f"observed_nat_sources: {','.join(sorted(sources))}\n"
"login_reply_count: 3\n"
"code_commit_reply_verified: true\n"
"branch_move_reply_verified: true\n"
"resident_login_reply_count: 10\n"
"recovery_reply_verified: true\n",
encoding="utf-8",
)
print(receipt.read_text(encoding="utf-8"), end="")
return
if not args.target:
raise SystemExit("--target is required unless --native-relay is used")
peer.settimeout(0.2 if args.resident_pipeline else 1.0)
target = (socket.gethostbyname(args.target), 0)
if args.resident_pipeline:
pipeline = [
LOGIN_MAGIC,
LOGIN_MAGIC,
LOGIN_MAGIC,
COMMIT_MAGIC,
BRANCH_MAGIC,
*([LOGIN_MAGIC] * 10),
RECOVERY_MAGIC,
]
acknowledged: set[int] = set()
while time.monotonic() < deadline and len(acknowledged) < len(pipeline):
for pipeline_sequence, pipeline_magic in enumerate(pipeline, start=1):
if time.monotonic() >= deadline:
break
peer.sendto(request(pipeline_sequence, pipeline_magic), target)
receive_until = min(deadline, time.monotonic() + 0.8)
while time.monotonic() < receive_until:
try:
packet, _ = peer.recvfrom(4096)
observed_sequence = reply_sequence_index(packet)
if not 1 <= observed_sequence <= len(pipeline):
raise RuntimeError(
"native ACK sequence is outside the pipeline"
)
reply_sequence = verify_reply(
packet,
pipeline[observed_sequence - 1],
)
except (TimeoutError, RuntimeError, IndexError):
continue
acknowledged.add(reply_sequence)
if reply_sequence == pipeline_sequence:
break
if len(acknowledged) != len(pipeline):
raise TimeoutError(
"native pipeline incomplete; acknowledged sequences: "
+ ",".join(str(item) for item in sorted(acknowledged))
)
receipt = pathlib.Path(args.receipt)
receipt.write_text(
"schema: guanghu.physical-native-icmp-peer/v2\n"
"status: PASS_100\n"
f"target: {args.target}\n"
"native_ack_marker: HLDP-NATIVE-ACK!\n"
"acknowledged_sequences: 1-16\n"
"login_reply_count: 3\n"
"code_commit_reply_verified: true\n"
"branch_move_reply_verified: true\n"
"resident_login_reply_count: 10\n"
"recovery_reply_verified: true\n",
encoding="utf-8",
)
print(receipt.read_text(encoding="utf-8"), end="")
return
if args.resume_resident_count is not None:
sequence = 6 + args.resume_resident_count
elif args.resume_after_branch:
sequence = 6
elif args.resume_after_commit:
sequence = 5
elif args.resume_after_login:
sequence = 4
else:
sequence = 1
phases: list[tuple[str, bytes]] = []
if not (
args.resume_after_login
or args.resume_after_commit
or args.resume_after_branch
or args.resume_resident_count is not None
):
phases.extend(
[
("login", LOGIN_MAGIC),
("login", LOGIN_MAGIC),
("login", LOGIN_MAGIC),
]
)
if (
not args.login_only
and not args.resume_after_commit
and not args.resume_after_branch
and args.resume_resident_count is None
):
phases.extend(
[
("code_commit", COMMIT_MAGIC),
("branch_move", BRANCH_MAGIC),
]
)
if args.resume_after_commit:
phases.append(("branch_move", BRANCH_MAGIC))
if args.resume_resident_count is not None:
phases.extend(
[("resident_login", LOGIN_MAGIC)]
* (10 - args.resume_resident_count)
)
phases.append(("recovery", RECOVERY_MAGIC))
elif (
args.resident
or args.resume_after_login
or args.resume_after_commit
or args.resume_after_branch
):
phases.extend([("resident_login", LOGIN_MAGIC)] * 10)
phases.append(("recovery", RECOVERY_MAGIC))
completed: list[str] = []
for phase, magic in phases:
exchange(peer, target, sequence, magic, deadline, args.retry_interval)
completed.append(phase)
sequence += 1
receipt = pathlib.Path(args.receipt)
receipt.write_text(
"schema: guanghu.physical-native-icmp-peer/v1\n"
"status: PASS_100\n"
f"target: {args.target}\n"
f"login_reply_count: {completed.count('login')}\n"
f"code_commit_reply_verified: {str('code_commit' in completed).lower()}\n"
f"branch_move_reply_verified: {str('branch_move' in completed).lower()}\n"
f"resident_login_resume_count: {args.resume_resident_count or 0}\n"
f"resident_login_reply_count: {completed.count('resident_login')}\n"
"resident_login_total_count: "
f"{(args.resume_resident_count or 0) + completed.count('resident_login')}\n"
f"recovery_reply_verified: {str('recovery' in completed).lower()}\n",
encoding="utf-8",
)
print(receipt.read_text(encoding="utf-8"), end="")
if __name__ == "__main__":
main()