Compare commits

...

1 Commits

Author SHA1 Message Date
chen08209
2faf4f8e9e Fix windows tun issues
Optimize android get system dns

Optimize more details
2025-06-14 18:55:25 +08:00
9 changed files with 115 additions and 88 deletions

View File

@@ -104,7 +104,7 @@ fun ConnectivityManager.resolveDns(network: Network?): List<String> {
fun InetAddress.asSocketAddressText(port: Int): String {
return when (this) {
is Inet6Address ->
"[${numericToTextFormat(this.address)}]:$port"
"[${numericToTextFormat(this)}]:$port"
is Inet4Address ->
"${this.hostAddress}:$port"
@@ -141,7 +141,8 @@ fun Context.getActionPendingIntent(action: String): PendingIntent {
}
}
private fun numericToTextFormat(src: ByteArray): String {
private fun numericToTextFormat(address: Inet6Address): String {
val src = address.address
val sb = StringBuilder(39)
for (i in 0 until 8) {
sb.append(
@@ -154,6 +155,10 @@ private fun numericToTextFormat(src: ByteArray): String {
sb.append(":")
}
}
if (address.scopeId > 0) {
sb.append("%")
sb.append(address.scopeId)
}
return sb.toString()
}

View File

@@ -100,6 +100,7 @@ data object VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
}
fun handleStart(options: VpnOptions): Boolean {
onUpdateNetwork();
if (options.enable != this.options?.enable) {
this.flClashService = null
}

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
@@ -90,37 +91,37 @@ class Request {
"https://ipinfo.io/json/": IpInfo.fromIpInfoIoJson,
};
Future<IpInfo?> checkIp({CancelToken? cancelToken}) async {
for (final source in _ipInfoSources.entries) {
try {
final response = await Dio()
.get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
options: Options(
responseType: ResponseType.json,
),
)
.timeout(
Duration(
seconds: 30,
),
);
if (response.statusCode != 200 || response.data == null) {
continue;
Future<Result<IpInfo?>> checkIp({CancelToken? cancelToken}) async {
var failureCount = 0;
final futures = _ipInfoSources.entries.map((source) async {
final Completer<Result<IpInfo?>> completer = Completer();
final future = Dio().get<Map<String, dynamic>>(
source.key,
cancelToken: cancelToken,
options: Options(
responseType: ResponseType.json,
),
);
future.then((res) {
if (res.statusCode == HttpStatus.ok && res.data != null) {
completer.complete(Result.success(source.value(res.data!)));
} else {
failureCount++;
if (failureCount == _ipInfoSources.length) {
completer.complete(Result.success(null));
}
}
if (response.data == null) {
continue;
}).catchError((e) {
failureCount++;
if (e == DioExceptionType.cancel) {
completer.complete(Result.error("cancelled"));
}
return source.value(response.data!);
} catch (e) {
commonPrint.log("checkIp error ===> $e");
if (e is DioException && e.type == DioExceptionType.cancel) {
throw "cancelled";
}
}
}
return null;
});
return completer.future;
});
final res = await Future.any(futures);
cancelToken?.cancel();
return res;
}
Future<bool> pingHelper() async {

View File

@@ -62,6 +62,7 @@ Future<void> _service(List<String> flags) async {
vpn?.addListener(
_VpnListenerWithService(
onDnsChanged: (String dns) {
print("handle dns $dns");
clashLibHandler.updateDns(dns);
},
),

View File

@@ -200,6 +200,24 @@ class Tun with _$Tun {
}
}
extension TunExt on Tun {
Tun getRealTun(RouteMode routeMode) {
final mRouteAddress = routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: routeAddress;
return switch (system.isDesktop) {
true => copyWith(
autoRoute: true,
routeAddress: [],
),
false => copyWith(
autoRoute: mRouteAddress.isEmpty ? true : false,
routeAddress: mRouteAddress,
),
};
}
}
@freezed
class FallbackFilter with _$FallbackFilter {
const factory FallbackFilter({

View File

@@ -94,7 +94,7 @@ final coreStateProvider = AutoDisposeProvider<CoreState>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef CoreStateRef = AutoDisposeProviderRef<CoreState>;
String _$updateParamsHash() => r'79fd7a5a8650fabac3a2ca7ce903c1d9eb363ed2';
String _$updateParamsHash() => r'012df72ab0e769a51c573f4692031506d7b1f1b4';
/// See also [updateParams].
@ProviderFor(updateParams)
@@ -126,7 +126,7 @@ final proxyStateProvider = AutoDisposeProvider<ProxyState>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ProxyStateRef = AutoDisposeProviderRef<ProxyState>;
String _$trayStateHash() => r'39ff84c50ad9c9cc666fa2538fe13ec0d7236b2e';
String _$trayStateHash() => r'61c99bbae2cb7ed69dc9ee0f2149510eb6a87df4';
/// See also [trayState].
@ProviderFor(trayState)

View File

@@ -105,10 +105,15 @@ CoreState coreState(Ref ref) {
@riverpod
UpdateParams updateParams(Ref ref) {
final routeMode = ref.watch(
networkSettingProvider.select(
(state) => state.routeMode,
),
);
return ref.watch(
patchClashConfigProvider.select(
(state) => UpdateParams(
tun: state.tun,
tun: state.tun.getRealTun(routeMode),
allowLan: state.allowLan,
findProcessMode: state.findProcessMode,
mode: state.mode,
@@ -153,9 +158,11 @@ TrayState trayState(Ref ref) {
final appSetting = ref.watch(
appSettingProvider,
);
final groups = ref.watch(
groupsProvider,
);
final groups = ref
.watch(
currentGroupsStateProvider,
)
.value;
final brightness = ref.watch(
appBrightnessProvider,
);

View File

@@ -315,19 +315,9 @@ class GlobalState {
final profileId = profile.id;
final configMap = await getProfileConfig(profileId);
final rawConfig = await handleEvaluate(configMap);
final routeAddress =
config.networkProps.routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: patchConfig.tun.routeAddress;
final realPatchConfig = !system.isDesktop
? patchConfig.copyWith.tun(
autoRoute: routeAddress.isEmpty ? true : false,
routeAddress: routeAddress,
)
: patchConfig.copyWith.tun(
autoRoute: true,
routeAddress: [],
);
final realPatchConfig = patchConfig.copyWith(
tun: patchConfig.tun.getRealTun(config.networkProps.routeMode),
);
rawConfig["external-controller"] = realPatchConfig.externalController.value;
rawConfig["external-ui"] = "";
rawConfig["interface-name"] = "";
@@ -411,21 +401,23 @@ class GlobalState {
for (final host in realPatchConfig.hosts.entries) {
rawConfig["hosts"][host.key] = host.value.splitByMultipleSeparators;
}
if (rawConfig["dns"] == null) {
rawConfig["dns"] = {};
}
final isEnableDns = rawConfig["dns"]["enable"] == true;
final overrideDns = globalState.config.overrideDns;
if (overrideDns) {
rawConfig["dns"] = realPatchConfig.dns.toJson();
if (overrideDns || !isEnableDns) {
final dns = switch (!isEnableDns) {
true => realPatchConfig.dns.copyWith(
nameserver: [...realPatchConfig.dns.nameserver, "system://"]),
false => realPatchConfig.dns,
};
rawConfig["dns"] = dns.toJson();
rawConfig["dns"]["nameserver-policy"] = {};
for (final entry in realPatchConfig.dns.nameserverPolicy.entries) {
for (final entry in dns.nameserverPolicy.entries) {
rawConfig["dns"]["nameserver-policy"][entry.key] =
entry.value.splitByMultipleSeparators;
}
} else {
if (rawConfig["dns"] == null) {
rawConfig["dns"] = {};
}
if (rawConfig["dns"]["enable"] != false) {
rawConfig["dns"]["enable"] = true;
}
}
var rules = [];
if (rawConfig["rules"] != null) {
@@ -509,6 +501,9 @@ class DetectionState {
debouncer.call(
FunctionTag.checkIp,
_checkIp,
duration: Duration(
milliseconds: 1200,
),
);
}
@@ -533,36 +528,35 @@ class DetectionState {
cancelToken = null;
}
cancelToken = CancelToken();
try {
state.value = state.value.copyWith(
isTesting: true,
);
final res = await request.checkIp(cancelToken: cancelToken);
if (res.isError) {
state.value = state.value.copyWith(
isTesting: true,
isLoading: true,
ipInfo: null,
);
final ipInfo = await request.checkIp(cancelToken: cancelToken);
state.value = state.value.copyWith(
isTesting: false,
);
if (ipInfo != null) {
state.value = state.value.copyWith(
isLoading: false,
ipInfo: ipInfo,
);
return;
}
_clearSetTimeoutTimer();
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
state.value = state.value.copyWith(
isLoading: false,
ipInfo: null,
);
});
} catch (e) {
if (e.toString() == "cancelled") {
state.value = state.value.copyWith(
isLoading: true,
ipInfo: null,
);
}
return;
}
final ipInfo = res.data;
state.value = state.value.copyWith(
isTesting: false,
);
if (ipInfo != null) {
state.value = state.value.copyWith(
isLoading: false,
ipInfo: ipInfo,
);
return;
}
_clearSetTimeoutTimer();
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
state.value = state.value.copyWith(
isLoading: false,
ipInfo: null,
);
});
}
_clearSetTimeoutTimer() {

View File

@@ -1,7 +1,7 @@
name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none'
version: 0.8.85+202506071
version: 0.8.86+202506142
environment:
sdk: '>=3.1.0 <4.0.0'