ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性

被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
This commit is contained in:
xmz7891
2026-10-06 19:01:05 +08:00
commit b62ac865ea
3195 changed files with 203292 additions and 0 deletions
@@ -0,0 +1,271 @@
package com.lanremote.watch;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* TLS 兼容层:让老手表也能连上中转。
*
* <p>为什么需要:
* 手表端报 {@code SSLHandshakeException: Chain validation failed}。
* 中转的证书链是
* {@code relay.xxmxxs.top <- GTS WE1 <- GTS Root R4 <- GlobalSign Root CA},
* 而很多廉价手表的系统根证书库缺 GTS Root R4(AOSP 2021 年才加),
* 于是找不到信任锚,握手直接失败。
*
* <p>做法:
* 把这两个根证书内嵌进 APK,校验时<b>系统库或内置库任一通过即可</b>。
* 这两个都是公开的合法根证书,<b>不是跳过校验</b>:
* 证书仍必须由可信 CA 签发、且域名匹配,只是把可信 CA 的集合
* 从"手表自带的那一份"扩大到"手表自带的 + 这两个"。
*
* <p>同时把难懂的异常翻译成用户能看懂的话。
*/
public final class TlsCompat {
private TlsCompat() {
}
// ---------------- 内置的根证书 ----------------
/** Google Trust Services Root R4(自签名根,EC P-384)。 */
private static final String GTS_ROOT_R4 =
"-----BEGIN CERTIFICATE-----\n" +
"MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD\n" +
"VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG\n" +
"A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw\n" +
"WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz\n" +
"IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\n" +
"AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi\n" +
"QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR\n" +
"HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\n" +
"BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D\n" +
"9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8\n" +
"p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD\n" +
"-----END CERTIFICATE-----\n";
/** GlobalSign Root CA(自签名根,RSA 2048,1998 年起就在各系统里)。 */
private static final String GLOBALSIGN_ROOT_CA =
"-----BEGIN CERTIFICATE-----\n" +
"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" +
"A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" +
"b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" +
"MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" +
"YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" +
"aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" +
"jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" +
"xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" +
"1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" +
"snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" +
"U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" +
"9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" +
"BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" +
"AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" +
"yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" +
"38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" +
"AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" +
"DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" +
"HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" +
"-----END CERTIFICATE-----\n";
private static SSLSocketFactory factory;
private static X509TrustManager bundled;
private static boolean ready;
private static boolean initTried;
// ---------------- 对外 ----------------
/**
* 打开一个连接,HTTPS 时挂上兼容的 SSLSocketFactory。
*
* <p>HTTP(局域网中转)不走这里,原样返回。
*/
public static HttpURLConnection open(String urlText) throws IOException {
HttpURLConnection connection =
(HttpURLConnection) new URL(urlText).openConnection();
connection.setUseCaches(false);
if (connection instanceof HttpsURLConnection) {
SSLSocketFactory ssl = factory();
if (ssl != null) {
((HttpsURLConnection) connection).setSSLSocketFactory(ssl);
}
}
return connection;
}
/** 拿到兼容的 SSLSocketFactory;准备失败就返回 null(退回系统默认)。 */
public static synchronized SSLSocketFactory factory() {
if (!initTried) {
initTried = true;
try {
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
store.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
int index = 0;
for (String pem : new String[]{GTS_ROOT_R4, GLOBALSIGN_ROOT_CA}) {
X509Certificate cert = (X509Certificate) cf.generateCertificate(
new ByteArrayInputStream(pem.getBytes("UTF-8")));
store.setCertificateEntry("lanremote-ca-" + (index++), cert);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(store);
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
bundled = (X509TrustManager) tm;
break;
}
}
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{new CompatTrustManager()}, null);
factory = context.getSocketFactory();
ready = true;
} catch (Throwable error) {
// 准备失败就退回系统默认,不影响正常设备
factory = null;
ready = false;
}
}
return ready ? factory : null;
}
/**
* 把 TLS / 网络异常翻译成用户能看懂的一句话。
*
* <p>手表上原本只会显示一长串英文类名,用户完全不知道该怎么办。
*/
public static String explain(Throwable error) {
if (error == null) {
return "未知错误";
}
String text = String.valueOf(error);
String lower = text.toLowerCase();
if (lower.contains("chain validation failed")
|| lower.contains("trust anchor")
|| lower.contains("certpathvalidator")) {
return "证书校验失败(中转的根证书手表不认识)。"
+ "可能是手表系统太旧,或网络被劫持。";
}
if (lower.contains("not yet valid") || lower.contains("expired")
|| lower.contains("certificate expired")) {
return "证书时间不对 —— 把手表的【日期和时间】调成自动获取,"
+ "再试一次。";
}
if (lower.contains("timeout") || lower.contains("timed out")) {
return "连接超时(网络太慢或被挡住)";
}
if (lower.contains("unable to resolve host")
|| lower.contains("unknownhost")) {
return "域名解析不了(检查手表能不能上网)";
}
if (lower.contains("connection refused")) {
return "对方拒绝了连接(电脑上 ykxmz 没开?)";
}
if (lower.contains("network is unreachable")
|| lower.contains("no route")) {
return "手表没联网(检查 WiFi)";
}
if (lower.contains("eof") || lower.contains("reset")) {
return "连接被中断(网络不稳)";
}
if (text.length() > 80) {
return text.substring(0, 80);
}
return text;
}
/**
* 组合信任:系统默认 或 内置根证书,任一通过即认可。
*
* <p>这是修这个问题的关键 ——
* 老手表缺 GTS Root R4,但内置的那份有,于是校验通过;
* 新设备走系统默认照样通过。两边都不削弱安全性。
*/
private static final class CompatTrustManager implements X509TrustManager {
private final X509TrustManager system = findSystem();
private static X509TrustManager findSystem() {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null); // null = 系统默认
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
return (X509TrustManager) tm;
}
}
} catch (Throwable ignored) {
// 拿不到就算了,下面用内置的
}
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {
if (system != null) {
system.checkClientTrusted(chain, authType);
return;
}
if (bundled != null) {
bundled.checkClientTrusted(chain, authType);
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws java.security.cert.CertificateException {
java.security.cert.CertificateException first = null;
if (system != null) {
try {
system.checkServerTrusted(chain, authType);
return;
} catch (java.security.cert.CertificateException error) {
first = error;
}
}
if (bundled != null) {
try {
bundled.checkServerTrusted(chain, authType);
return;
} catch (java.security.cert.CertificateException error) {
if (first == null) {
first = error;
}
}
}
throw (first != null ? first
: new java.security.cert.CertificateException(
"证书校验失败"));
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] a = system == null
? new X509Certificate[0] : system.getAcceptedIssuers();
X509Certificate[] b = bundled == null
? new X509Certificate[0] : bundled.getAcceptedIssuers();
X509Certificate[] all = new X509Certificate[a.length + b.length];
System.arraycopy(a, 0, all, 0, a.length);
System.arraycopy(b, 0, all, a.length, b.length);
return all;
}
}
}