mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 16:00:43 +00:00
被控端 - 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%) - 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败) - 每分钟回头找局域网,自动切回快 400 倍的路径 - 画面心跳帧:静止时省流量,客户端仍知道流是活的 - 锁屏时给出可操作的提示 手表端 - 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException - 连接错误翻译成人话(证书/时间/解析/超时分别提示) - 经中转连上后用服务器报的地址刷新已配对记录 - 新增局域网连接页面、服务器延迟检测 服务器端 - 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502) - 网络调优脚本 net-tune.sh(开机自动应用) - 管理页:批量删除、网络体检、下载区 - 隧道改 HTTP/2 + 双连接 文档 - 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
192 lines
7.2 KiB
Python
192 lines
7.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""第一步:让手表知道"电脑在哪个局域网、开在哪个端口"。
|
|
|
|
为什么需要:
|
|
用户要求 —— 手表连上之后,如果发现电脑和自己**在同一个网络**,
|
|
就切换成局域网直连(快得多);不在同一网络就继续用中转。
|
|
|
|
要判断"同一个网络",手表得知道电脑的局域网 IP。
|
|
所以被控端登记时要把自己的局域网 IP 和端口报给中转,
|
|
中转再转给手表。
|
|
|
|
改动:
|
|
1. windows/lanremote/relay.py 登记时带上 lanIps 和 port
|
|
2. relay/relay.py Device 存下来,并在设备信息里返回
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
|
CLIENT = ROOT / "windows" / "lanremote" / "relay.py"
|
|
SERVER = ROOT / "relay" / "relay.py"
|
|
|
|
# ---------------- 1. 被控端:算自己的局域网 IP ----------------
|
|
|
|
CLIENT_ANCHOR = ''' def _ensure_device_id(self) -> str:'''
|
|
|
|
CLIENT_NEW = ''' def _lan_ips(self) -> list:
|
|
"""本机在局域网里的 IP(可能有多张网卡)。
|
|
|
|
手表靠这个判断"电脑是不是和我同一个网络"。
|
|
拿不到就返回空列表 —— 中转那边会当成"不知道",
|
|
手表就继续走中转,不会出错。
|
|
"""
|
|
found = []
|
|
try:
|
|
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
try:
|
|
# 不会真的发包,只是让系统选出默认出口网卡
|
|
probe.connect(("8.8.8.8", 80))
|
|
addr = probe.getsockname()[0]
|
|
if addr and not addr.startswith("127."):
|
|
found.append(addr)
|
|
finally:
|
|
probe.close()
|
|
except OSError:
|
|
pass
|
|
try:
|
|
for info in socket.getaddrinfo(socket.gethostname(), None,
|
|
socket.AF_INET):
|
|
addr = info[4][0]
|
|
if (addr and not addr.startswith("127.")
|
|
and not addr.startswith("169.254.")
|
|
and addr not in found):
|
|
found.append(addr)
|
|
except OSError:
|
|
pass
|
|
return found
|
|
|
|
def _ensure_device_id(self) -> str:'''
|
|
|
|
CLIENT_PAYLOAD_OLD = ''' payload = json.dumps({
|
|
"deviceId": self.device_id,
|
|
"code": self.code,
|
|
"name": self.device_name or self.device_id,
|
|
"version": _version(),
|
|
}, ensure_ascii=False).encode("utf-8")'''
|
|
|
|
CLIENT_PAYLOAD_NEW = ''' try:
|
|
listen_port = int(self.store.get("port") or 0)
|
|
except (TypeError, ValueError):
|
|
listen_port = 0
|
|
payload = json.dumps({
|
|
"deviceId": self.device_id,
|
|
"code": self.code,
|
|
"name": self.device_name or self.device_id,
|
|
"version": _version(),
|
|
# 手表用这两个判断要不要切成局域网直连
|
|
"lanIps": self._lan_ips(),
|
|
"port": listen_port,
|
|
}, ensure_ascii=False).encode("utf-8")'''
|
|
|
|
# ---------------- 2. 中转:Device 存字段 ----------------
|
|
|
|
SRV_SLOTS_OLD = ''' __slots__ = ("device_id", "code", "name", "version", "token",
|
|
"last_seen", "connected_at", "to_client", "to_agent",
|
|
"event", "bytes_in", "bytes_out", "current_session",
|
|
"active_session")'''
|
|
|
|
SRV_SLOTS_NEW = ''' __slots__ = ("device_id", "code", "name", "version", "token",
|
|
"last_seen", "connected_at", "to_client", "to_agent",
|
|
"event", "bytes_in", "bytes_out", "current_session",
|
|
"active_session", "lan_ips", "port")'''
|
|
|
|
SRV_INIT_OLD = ''' self.token = secrets.token_hex(24)
|
|
self.last_seen = time.time()'''
|
|
|
|
SRV_INIT_NEW = ''' self.token = secrets.token_hex(24)
|
|
# 这台电脑的局域网 IP 和监听端口。
|
|
# 手表拿它判断"要不要切成局域网直连"。
|
|
self.lan_ips: list = []
|
|
self.port: int = 0
|
|
self.last_seen = time.time()'''
|
|
|
|
# 注册时接收
|
|
SRV_REG_OLD = ''' name = str(body.get("name") or "").strip()[:40] or "未命名设备"
|
|
version = str(body.get("version") or "").strip()[:20]'''
|
|
|
|
SRV_REG_NEW = ''' name = str(body.get("name") or "").strip()[:40] or "未命名设备"
|
|
version = str(body.get("version") or "").strip()[:20]
|
|
# 这台电脑的局域网 IP + 端口(手表判断要不要走局域网直连)
|
|
raw_ips = body.get("lanIps")
|
|
lan_ips = []
|
|
if isinstance(raw_ips, list):
|
|
for item in raw_ips[:8]:
|
|
text = str(item).strip()
|
|
# 只收看起来像 IPv4 的,别把乱七八糟的东西转给手表
|
|
parts = text.split(".")
|
|
if len(parts) == 4 and all(
|
|
p.isdigit() and 0 <= int(p) <= 255 for p in parts):
|
|
lan_ips.append(text)
|
|
try:
|
|
listen_port = int(body.get("port") or 0)
|
|
except (TypeError, ValueError):
|
|
listen_port = 0
|
|
if not (0 < listen_port < 65536):
|
|
listen_port = 0'''
|
|
|
|
# 存到 Device 上(在设备对象拿到之后)
|
|
SRV_STORE_ANCHOR = ''' device.last_seen = time.time()'''
|
|
|
|
SRV_STORE_NEW = ''' device.last_seen = time.time()
|
|
# 每次登记都刷新局域网信息(IP 可能变,端口也可能改)
|
|
device.lan_ips = lan_ips
|
|
device.port = listen_port'''
|
|
|
|
# 快照里带给手表
|
|
SRV_SNAP_OLD = ''' "code": device.code,
|
|
"name": device.name,'''
|
|
|
|
SRV_SNAP_NEW = ''' "code": device.code,
|
|
"name": device.name,
|
|
# 手表用这两个判断能不能走局域网直连
|
|
"lanIps": list(device.lan_ips),
|
|
"port": device.port,'''
|
|
|
|
|
|
def patch(path: Path, pairs, label: str) -> bool:
|
|
text = path.read_text(encoding="utf-8")
|
|
ok = True
|
|
for old, new, tag in pairs:
|
|
if old not in text:
|
|
print(f" [{label}] [跳过] {tag} —— 锚点没找到")
|
|
ok = False
|
|
continue
|
|
text = text.replace(old, new, 1)
|
|
print(f" [{label}] {tag}")
|
|
path.write_text(text, encoding="utf-8", newline="\n")
|
|
return ok
|
|
|
|
|
|
def main() -> int:
|
|
ok = True
|
|
|
|
ok &= patch(CLIENT, [
|
|
(CLIENT_ANCHOR, CLIENT_NEW, "_lan_ips() 方法"),
|
|
(CLIENT_PAYLOAD_OLD, CLIENT_PAYLOAD_NEW, "登记带上 lanIps/port"),
|
|
], "被控端")
|
|
|
|
ok &= patch(SERVER, [
|
|
(SRV_SLOTS_OLD, SRV_SLOTS_NEW, "Device 加字段"),
|
|
(SRV_INIT_OLD, SRV_INIT_NEW, "Device.__init__"),
|
|
(SRV_REG_OLD, SRV_REG_NEW, "注册接收 lanIps/port"),
|
|
(SRV_STORE_ANCHOR, SRV_STORE_NEW, "存到 Device"),
|
|
(SRV_SNAP_OLD, SRV_SNAP_NEW, "快照里返回"),
|
|
], "中转")
|
|
|
|
import ast
|
|
for label, path in (("被控端", CLIENT), ("中转", SERVER)):
|
|
try:
|
|
ast.parse(path.read_text(encoding="utf-8"))
|
|
print(f" {label} 语法 OK")
|
|
except SyntaxError as exc:
|
|
print(f" {label} 语法错误: 行 {exc.lineno}: {exc.msg}")
|
|
ok = False
|
|
return 0 if ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|