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:
esefxdzandClaude Opus 5.5 committed 2026-10-08 13:20:52 +03:00
1 parent 6d8a923475
commit e14e900276
3 files changed
+66 -4

No files matched your search

+18
View File
@@ -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
### Added
+47 -3
View File
@@ -86,15 +86,22 @@ abstract final class LocalSocksTester {
}
}
/// Full user-facing test: authenticated local SOCKS handshake plus an
/// outbound TCP CONNECT through the active ReVolt route.
/// Full user-facing test: authenticated local SOCKS handshake, an outbound
/// 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({
String host = '127.0.0.1',
required int port,
required String username,
required String password,
String targetHost = 'example.com',
int targetPort = 443,
int targetPort = 80,
}) async {
const totalTimeout = Duration(seconds: 8);
final stopwatch = Stopwatch()..start();
@@ -171,6 +178,43 @@ abstract final class LocalSocksTester {
}
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();
return LocalSocksTestResult(
ok: true,
+1 -1
View File
@@ -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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 3.3.5+31
version: 3.3.6+32
environment:
sdk: ">=3.0.0 <4.0.0"