Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e14e900276 | ||
|
|
6d8a923475 |
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
|
||||
|
||||
### Added
|
||||
|
||||
@@ -172,13 +172,13 @@ class MainActivity : FlutterActivity() {
|
||||
}
|
||||
|
||||
val publicVersion = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.notification_status_icon)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setContentTitle("Revolt VPN")
|
||||
.setContentText("VPN is active")
|
||||
.build()
|
||||
|
||||
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.notification_status_icon)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setColorized(true)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -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,
|
||||
|
||||
@@ -120,7 +120,7 @@ class VpnConnection extends ChangeNotifier {
|
||||
await _vless.initializeVless(
|
||||
providerBundleIdentifier: 'com.paladinvpn.app',
|
||||
notificationIconResourceType: 'drawable',
|
||||
notificationIconResourceName: 'notification_status_icon',
|
||||
notificationIconResourceName: 'notification_icon',
|
||||
);
|
||||
if (_disposed) return;
|
||||
_initialized = true;
|
||||
|
||||
+2
-1
@@ -655,7 +655,8 @@ class XrayVPNService : VpnService() {
|
||||
android.app.Notification.Builder(this)
|
||||
}
|
||||
|
||||
val icon = android.R.drawable.ic_dialog_info
|
||||
val icon = resources.getIdentifier("notification_icon", "drawable", packageName)
|
||||
.takeIf { it != 0 } ?: android.R.drawable.ic_dialog_info
|
||||
|
||||
val notification = builder
|
||||
.setContentTitle("VPN Service")
|
||||
|
||||
+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.
|
||||
# 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"
|
||||
|
||||
Reference in new issue
Block a user