chore(release): prepare 3.3.6
- fix(app): Test Local SOCKS forces a round trip instead of trusting the CONNECT reply - changelog: notification icon + SOCKS test fixes Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6d8a923475
commit
e14e900276
3 files changed
+66
-4
No files matched your search
@@ -5,6 +5,24 @@ Versions follow [semver](https://semver.org/): MAJOR.MINOR.PATCH
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [3.3.6] — 2026-10-08
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **VPN service notification icon.** The ongoing notification now uses the
|
||||||
|
bundled `notification_icon` drawable instead of `notification_status_icon`,
|
||||||
|
and the vendored runtime resolves that drawable by name at runtime (falling
|
||||||
|
back to the platform icon only if it is missing), so the foreground-service
|
||||||
|
notification no longer shows the generic Android info icon.
|
||||||
|
- **"Test Local SOCKS" no longer claims an outbound it never tested.** Xray
|
||||||
|
answers a SOCKS `CONNECT` before it dials upstream, so trusting the `0x00`
|
||||||
|
reply reported success for unroutable addresses and for hostnames that can
|
||||||
|
never resolve — meaning the test said OK while the server was down, which is
|
||||||
|
exactly when it gets run. It now forces a request/response round trip through
|
||||||
|
the tunnel and reports distinctly when the outbound never replies, drops the
|
||||||
|
connection, or returns unexpected data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [3.3.5] — 2026-09-06
|
## [3.3.5] — 2026-09-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -86,15 +86,22 @@ abstract final class LocalSocksTester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Full user-facing test: authenticated local SOCKS handshake plus an
|
/// Full user-facing test: authenticated local SOCKS handshake, an outbound
|
||||||
/// outbound TCP CONNECT through the active ReVolt route.
|
/// CONNECT through the active ReVolt route, and — critically — a real
|
||||||
|
/// request/response exchange over that CONNECT.
|
||||||
|
///
|
||||||
|
/// The exchange is not optional politeness. Xray answers a SOCKS CONNECT
|
||||||
|
/// before it dials upstream, so the reply code alone says nothing about the
|
||||||
|
/// outbound: it returns success for unroutable addresses and for hostnames
|
||||||
|
/// that can never resolve. Without a round trip this test reports OK while
|
||||||
|
/// the server is down, which is precisely when a user runs it.
|
||||||
static Future<LocalSocksTestResult> test({
|
static Future<LocalSocksTestResult> test({
|
||||||
String host = '127.0.0.1',
|
String host = '127.0.0.1',
|
||||||
required int port,
|
required int port,
|
||||||
required String username,
|
required String username,
|
||||||
required String password,
|
required String password,
|
||||||
String targetHost = 'example.com',
|
String targetHost = 'example.com',
|
||||||
int targetPort = 443,
|
int targetPort = 80,
|
||||||
}) async {
|
}) async {
|
||||||
const totalTimeout = Duration(seconds: 8);
|
const totalTimeout = Duration(seconds: 8);
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
@@ -171,6 +178,43 @@ abstract final class LocalSocksTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _consumeAddress(reader, replyHead[3], timeout: remaining());
|
await _consumeAddress(reader, replyHead[3], timeout: remaining());
|
||||||
|
|
||||||
|
// The CONNECT reply above is not evidence of an outbound. Force a round
|
||||||
|
// trip and require the peer's answer before claiming the route works.
|
||||||
|
socket.add(
|
||||||
|
utf8.encode(
|
||||||
|
'GET / HTTP/1.1\r\nHost: $targetHost\r\nConnection: close\r\n\r\n',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await socket.flush();
|
||||||
|
|
||||||
|
List<int> banner;
|
||||||
|
try {
|
||||||
|
banner = await reader.readExactly(5, timeout: remaining());
|
||||||
|
} on TimeoutException {
|
||||||
|
return const LocalSocksTestResult(
|
||||||
|
ok: false,
|
||||||
|
latencyMs: null,
|
||||||
|
message: 'Local SOCKS5 is up, but the ReVolt outbound never replied.',
|
||||||
|
);
|
||||||
|
} on SocketException {
|
||||||
|
return const LocalSocksTestResult(
|
||||||
|
ok: false,
|
||||||
|
latencyMs: null,
|
||||||
|
message:
|
||||||
|
'Local SOCKS5 is up, but the ReVolt outbound dropped the connection.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.fromCharCodes(banner) != 'HTTP/') {
|
||||||
|
return const LocalSocksTestResult(
|
||||||
|
ok: false,
|
||||||
|
latencyMs: null,
|
||||||
|
message:
|
||||||
|
'Local SOCKS5 is up, but the ReVolt outbound returned unexpected data.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
return LocalSocksTestResult(
|
return LocalSocksTestResult(
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 3.3.5+31
|
version: 3.3.6+32
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|||||||
Reference in new issue
Block a user