From e8958aa8f022262f20810eafe07af7632f79c64e Mon Sep 17 00:00:00 2001 From: zhuhjay Date: Sat, 7 Nov 2026 18:14:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(cmcc):=20=E9=9D=92=E9=BE=99=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=20OpenSSL=203.x=20legacy=20renegotiation=20=E5=85=BC?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 兼容) --- cmcc/scrip_cmcc_mark.py | 61 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/cmcc/scrip_cmcc_mark.py b/cmcc/scrip_cmcc_mark.py index 45d41f1..7d01dd3 100644 --- a/cmcc/scrip_cmcc_mark.py +++ b/cmcc/scrip_cmcc_mark.py @@ -49,6 +49,63 @@ except ImportError: from datetime import datetime, timedelta, timezone 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: tried += 1 try: - r = requests.post(seed["url"], data=seed["body"].encode("utf-8"), - headers=seed["headers"], timeout=30) + r = post_seed(seed["url"], seed["body"].encode("utf-8"), + seed["headers"], timeout=30) except Exception as e: stats["net_error"] = stats.get("net_error", 0) + 1 log("[A] #%d 网络错误 %r" % (tried, e))