feat(lite): merge TUN mode with IPv6 routing
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""SagerNet UDP over TCP v2 帧编解码。
|
||||
|
||||
UoT 只负责把 UDP 数据报装入已有的可靠 TCP 字节流;外层连接仍由
|
||||
HTTP CONNECT + TLS 建立。此模块不依赖 sing-box。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
import socket
|
||||
import struct
|
||||
from dataclasses import dataclass
|
||||
|
||||
MAGIC_ADDRESS = "sp.v2.udp-over-tcp.arpa"
|
||||
MAX_DATAGRAM = 65535
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Address:
|
||||
host: str
|
||||
port: int
|
||||
|
||||
|
||||
def encode_address(host: str, port: int) -> bytes:
|
||||
host = str(host).strip()
|
||||
if not 0 <= int(port) <= 65535:
|
||||
raise ValueError("端口超出范围")
|
||||
try:
|
||||
addr = ipaddress.ip_address(host)
|
||||
except ValueError:
|
||||
raw = host.encode("idna")
|
||||
if not 1 <= len(raw) <= 255:
|
||||
raise ValueError("域名长度超出 UoT 限制")
|
||||
return b"\x02" + bytes([len(raw)]) + raw + struct.pack("!H", int(port))
|
||||
if addr.version == 4:
|
||||
return b"\x00" + addr.packed + struct.pack("!H", int(port))
|
||||
return b"\x01" + addr.packed + struct.pack("!H", int(port))
|
||||
|
||||
|
||||
def decode_address(data: bytes, offset: int = 0) -> tuple[Address, int]:
|
||||
if offset >= len(data):
|
||||
raise ValueError("UoT 地址头不完整")
|
||||
atyp = data[offset]
|
||||
offset += 1
|
||||
if atyp == 0:
|
||||
size = 4
|
||||
elif atyp == 1:
|
||||
size = 16
|
||||
elif atyp == 2:
|
||||
if offset >= len(data):
|
||||
raise ValueError("UoT 域名长度缺失")
|
||||
size = data[offset]
|
||||
offset += 1
|
||||
else:
|
||||
raise ValueError(f"未知 UoT 地址类型: {atyp}")
|
||||
end = offset + size
|
||||
if end + 2 > len(data):
|
||||
raise ValueError("UoT 地址头不完整")
|
||||
raw = data[offset:end]
|
||||
port = struct.unpack("!H", data[end:end + 2])[0]
|
||||
if atyp == 0:
|
||||
host = str(ipaddress.IPv4Address(raw))
|
||||
elif atyp == 1:
|
||||
host = str(ipaddress.IPv6Address(raw))
|
||||
else:
|
||||
host = raw.decode("idna")
|
||||
return Address(host, port), end + 2
|
||||
|
||||
|
||||
def encode_request(is_connect: bool, destination: Address) -> bytes:
|
||||
return (b"\x01" if is_connect else b"\x00") + encode_address(
|
||||
destination.host, destination.port
|
||||
)
|
||||
|
||||
|
||||
def encode_connect_frame(payload: bytes) -> bytes:
|
||||
if len(payload) > MAX_DATAGRAM:
|
||||
raise ValueError("UoT 数据报超过 65535 字节")
|
||||
return struct.pack("!H", len(payload)) + payload
|
||||
|
||||
|
||||
def encode_datagram_frame(destination: Address, payload: bytes) -> bytes:
|
||||
if len(payload) > MAX_DATAGRAM:
|
||||
raise ValueError("UoT 数据报超过 65535 字节")
|
||||
return encode_address(destination.host, destination.port) + struct.pack("!H", len(payload)) + payload
|
||||
|
||||
|
||||
def read_exact(stream, size: int) -> bytes:
|
||||
chunks = []
|
||||
remaining = size
|
||||
while remaining:
|
||||
chunk = stream.recv(remaining)
|
||||
if not chunk:
|
||||
raise ConnectionError("UoT TCP 流已关闭")
|
||||
chunks.append(chunk)
|
||||
remaining -= len(chunk)
|
||||
return b"".join(chunks)
|
||||
|
||||
|
||||
def read_connect_frame(stream) -> bytes:
|
||||
size = struct.unpack("!H", read_exact(stream, 2))[0]
|
||||
return read_exact(stream, size)
|
||||
|
||||
|
||||
def read_datagram_frame(stream) -> tuple[Address, bytes]:
|
||||
# 地址是变长的,只能先读 ATYP,再按类型补齐。
|
||||
head = read_exact(stream, 1)
|
||||
atyp = head[0]
|
||||
if atyp == 0:
|
||||
rest = read_exact(stream, 6)
|
||||
elif atyp == 1:
|
||||
rest = read_exact(stream, 18)
|
||||
elif atyp == 2:
|
||||
length = read_exact(stream, 1)
|
||||
rest = length + read_exact(stream, length[0] + 2)
|
||||
else:
|
||||
raise ValueError(f"未知 UoT 地址类型: {atyp}")
|
||||
address, _ = decode_address(head + rest)
|
||||
size = struct.unpack("!H", read_exact(stream, 2))[0]
|
||||
return address, read_exact(stream, size)
|
||||
Reference in New Issue
Block a user