fix(cmcc): 青龙容器 OpenSSL 3.x legacy renegotiation 兼容

client.app.coc.10086.cn 用 legacy TLS renegotiation,青龙容器(Python3.11 +
OpenSSL 3.x)默认拒绝 → UNSAFE_LEGACY_RENEGOTIATION_DISABLED 全发网络错误
(真机日志 2026-11-07 18:00)。修复:
- 内置 ssl 上下文放行 SSL_OP_LEGACY_SERVER_CONNECT(0x4)
- 预建 urllib3 HTTPSConnectionPool 专池([A] 仍无状态,不用 Session)
- post_seed() 统一出口:legacy 池优先,异常回落 requests(verify=False)
- _U3Response 最小适配 urllib3 响应到 requests 风格(classify 兼容)
This commit is contained in:
2026-11-07 18:14:21 +08:00
parent 27e3c95585
commit e8958aa8f0

View File

@@ -49,6 +49,63 @@ except ImportError:
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import requests import requests
import urllib3
# ---------------------------------------------------------------- SSL 兼容
# 中国移动 client.app.coc.10086.cn 用 legacy TLS renegotiation(OpenSSL 3.x 默认拒:
# UNSAFE_LEGACY_RENEGOTIATION_DISABLED,青龙容器 Python3.11 实测命中)。
# 自定义 ssl_context 放行 legacy 握手,全部请求统一走它。
def _make_ssl_context():
try:
import ssl
ctx = ssl.create_default_context()
ctx.options |= 0x4 # SSL_OP_LEGACY_SERVER_CONNECT(OpenSSL 3.x 需显式放行)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
except Exception:
return None
_SSL_CTX = _make_ssl_context()
if _SSL_CTX is not None:
try:
_POOL = urllib3.HTTPSConnectionPool(
'client.app.coc.10086.cn', 443, ssl_context=_SSL_CTX, maxsize=4)
except Exception:
_POOL = None
else:
_POOL = None
def post_seed(url, body_bytes, headers, timeout=30):
"""[A] 重放的统一出口。优先走预建 legacy-SSL 连接池;异常回落标准 requests。
无状态(不用 Session)——素材 Cookie 必须原样直发,jar 会污染。"""
if _POOL is not None:
try:
r = _POOL.urlopen('POST', url, body=body_bytes, headers=headers, timeout=timeout)
# urllib3 响应包一层成 requests 风格(classify 只用 headers/content/status_code/raw)
return _U3Response(r)
except Exception:
pass
return requests.post(url, data=body_bytes, headers=headers, timeout=timeout,
verify=False)
class _U3Response:
"""urllib3 HTTPResponse → requests 风格最小适配(headers/content/status_code/raw)。"""
def __init__(self, u3):
self.status_code = u3.status
self.content = u3.data
self._headers = u3.headers
self._u3 = u3
@property
def headers(self):
# urllib3 HttpHeaders 大小写不敏感 get,直接透传
return self._headers
@property
def raw(self):
return self._u3
# ---------------------------------------------------------------- 常量 # ---------------------------------------------------------------- 常量
@@ -248,8 +305,8 @@ def replay_auto_login(seed, stats):
while tried < REPLAY_MAX and time.time() - start < WALL_LIMIT: while tried < REPLAY_MAX and time.time() - start < WALL_LIMIT:
tried += 1 tried += 1
try: try:
r = requests.post(seed["url"], data=seed["body"].encode("utf-8"), r = post_seed(seed["url"], seed["body"].encode("utf-8"),
headers=seed["headers"], timeout=30) seed["headers"], timeout=30)
except Exception as e: except Exception as e:
stats["net_error"] = stats.get("net_error", 0) + 1 stats["net_error"] = stats.get("net_error", 0) + 1
log("[A] #%d 网络错误 %r" % (tried, e)) log("[A] #%d 网络错误 %r" % (tried, e))