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:
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user