Files
lou 96d44569fd L2/L3 通道: HTTP/DNS 套壳转发(本地分流代理 + 云端转发器)
- proto.py: 帧协议 [type][req_id][len][payload], req_id 多路复用
- client.py: TLS 长连 + AUTH + 断线自动重连(全标准库, OpenWrt 可跑)
- proxy_http.py: 本地 HTTP 透明代理(明文 80 走通道, 失败回 502)
- proxy_dns.py: DNS 分流(内网域 -> 学校 DNS, 公网域 -> 云通道递归)
- server.py: 云端 AUTH + HTTPS 套壳(统一 UA, 剔 hop-by-hop)+ DNS 递归
- deploy/hook.sh: REDIRECT 规则(内网段放行, 认证 eportal 绝不走通道)
- 30 个新单测(40 总)+ smoke_channel 端到端冒烟 PASS
2026-09-09 14:55:02 +08:00

62 lines
1.8 KiB
Python

"""proto 帧协议单测: 打包/切帧/边界。"""
from __future__ import annotations
import unittest
from channel.proto import (
FrameReader,
HEADER_LEN,
MAX_PAYLOAD,
TYPE_DNS_REQ,
TYPE_HTTP_RESP,
pack_frame,
)
class ProtoTest(unittest.TestCase):
def test_pack_roundtrip(self) -> None:
payload = b"\x01\x02abc"
frame = pack_frame(TYPE_DNS_REQ, 7, payload)
self.assertEqual(len(frame), HEADER_LEN + len(payload))
reader = FrameReader()
reader.feed(frame)
got = reader.poll()
assert got is not None
self.assertEqual(got, (TYPE_DNS_REQ, 7, payload))
def test_reader_handles_partial_and_multiple(self) -> None:
f1 = pack_frame(TYPE_HTTP_RESP, 1, b"hello")
f2 = pack_frame(TYPE_DNS_REQ, 2, b"")
reader = FrameReader()
reader.feed(f1[:5])
self.assertIsNone(reader.poll())
reader.feed(f1[5:] + f2[:3])
got1 = reader.poll()
assert got1 is not None
self.assertEqual(got1, (TYPE_HTTP_RESP, 1, b"hello"))
self.assertIsNone(reader.poll()) # f2 还不完整
reader.feed(f2[3:])
got2 = reader.poll()
assert got2 is not None
self.assertEqual(got2, (TYPE_DNS_REQ, 2, b""))
def test_empty_payload_frame(self) -> None:
frame = pack_frame(0, 0)
reader = FrameReader()
reader.feed(frame)
got = reader.poll()
assert got is not None
self.assertEqual(got, (0, 0, b""))
def test_oversize_payload_rejected(self) -> None:
with self.assertRaises(ValueError):
pack_frame(0, 0, b"x" * (MAX_PAYLOAD + 1))
def test_bad_type_rejected(self) -> None:
with self.assertRaises(ValueError):
pack_frame(300, 0)
if __name__ == "__main__":
unittest.main()