Add proxy routing and sing-box support

This commit is contained in:
Fabian Freund
2026-05-22 18:16:31 +02:00
parent 51289f1266
commit a5974617aa
262 changed files with 32003 additions and 3962 deletions
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:fast_equatable/fast_equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'proxy_dns_override.g.dart';
/// Mirrors sing-box's `dns.strategy` field.
enum ProxyDnsDomainStrategy { auto, preferIpv4, preferIpv6, ipv4Only, ipv6Only }
extension ProxyDnsDomainStrategyExt on ProxyDnsDomainStrategy {
String get singboxValue => switch (this) {
ProxyDnsDomainStrategy.auto => '',
ProxyDnsDomainStrategy.preferIpv4 => 'prefer_ipv4',
ProxyDnsDomainStrategy.preferIpv6 => 'prefer_ipv6',
ProxyDnsDomainStrategy.ipv4Only => 'ipv4_only',
ProxyDnsDomainStrategy.ipv6Only => 'ipv6_only',
};
}
@JsonSerializable(includeIfNull: true)
class ProxyDnsOverride with FastEquatable {
/// Single DNS server resolved through this profile's own outbound.
final String? remoteServerAddress;
final ProxyDnsDomainStrategy domainStrategy;
ProxyDnsOverride({
this.remoteServerAddress,
this.domainStrategy = ProxyDnsDomainStrategy.preferIpv4,
});
factory ProxyDnsOverride.fromJson(Map<String, dynamic> json) =>
_$ProxyDnsOverrideFromJson(json);
Map<String, dynamic> toJson() => _$ProxyDnsOverrideToJson(this);
@override
List<Object?> get hashParameters => [remoteServerAddress, domainStrategy];
}
@@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'proxy_dns_override.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProxyDnsOverride _$ProxyDnsOverrideFromJson(Map<String, dynamic> json) =>
ProxyDnsOverride(
remoteServerAddress: json['remoteServerAddress'] as String?,
domainStrategy:
$enumDecodeNullable(
_$ProxyDnsDomainStrategyEnumMap,
json['domainStrategy'],
) ??
ProxyDnsDomainStrategy.preferIpv4,
);
Map<String, dynamic> _$ProxyDnsOverrideToJson(
ProxyDnsOverride instance,
) => <String, dynamic>{
'remoteServerAddress': instance.remoteServerAddress,
'domainStrategy': _$ProxyDnsDomainStrategyEnumMap[instance.domainStrategy]!,
};
const _$ProxyDnsDomainStrategyEnumMap = {
ProxyDnsDomainStrategy.auto: 'auto',
ProxyDnsDomainStrategy.preferIpv4: 'preferIpv4',
ProxyDnsDomainStrategy.preferIpv6: 'preferIpv6',
ProxyDnsDomainStrategy.ipv4Only: 'ipv4Only',
ProxyDnsDomainStrategy.ipv6Only: 'ipv6Only',
};
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:weblibre/features/proxy/data/proxy_connection.dart';
part 'proxy_routing_settings.g.dart';
enum ProxyRegularTabRoutingMode { container, all }
@CopyWith()
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
class ProxyRoutingSettings with FastEquatable {
final ProxyRegularTabRoutingMode regularTabsMode;
@JsonKey(
fromJson: _proxyConnectionIdFromJson,
toJson: _proxyConnectionIdToJson,
)
final ProxyConnectionId? regularTabsProxyConnectionId;
@JsonKey(
fromJson: _proxyConnectionIdFromJson,
toJson: _proxyConnectionIdToJson,
)
final ProxyConnectionId? privateTabsProxyConnectionId;
ProxyRoutingSettings({
required this.regularTabsMode,
required this.regularTabsProxyConnectionId,
required this.privateTabsProxyConnectionId,
});
ProxyRoutingSettings.withDefaults({
ProxyRegularTabRoutingMode? regularTabsMode,
this.regularTabsProxyConnectionId,
this.privateTabsProxyConnectionId,
}) : regularTabsMode =
regularTabsMode ?? ProxyRegularTabRoutingMode.container;
factory ProxyRoutingSettings.fromJson(Map<String, dynamic> json) =>
_$ProxyRoutingSettingsFromJson(json);
Map<String, dynamic> toJson() => _$ProxyRoutingSettingsToJson(this);
@override
List<Object?> get hashParameters => [
regularTabsMode,
regularTabsProxyConnectionId,
privateTabsProxyConnectionId,
];
}
ProxyConnectionId? _proxyConnectionIdFromJson(String? json) =>
ProxyConnectionId.decode(json);
String? _proxyConnectionIdToJson(ProxyConnectionId? object) => object?.encode();
@@ -0,0 +1,136 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'proxy_routing_settings.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$ProxyRoutingSettingsCWProxy {
ProxyRoutingSettings regularTabsMode(
ProxyRegularTabRoutingMode regularTabsMode,
);
ProxyRoutingSettings regularTabsProxyConnectionId(
ProxyConnectionId? regularTabsProxyConnectionId,
);
ProxyRoutingSettings privateTabsProxyConnectionId(
ProxyConnectionId? privateTabsProxyConnectionId,
);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `ProxyRoutingSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// ProxyRoutingSettings(...).copyWith(id: 12, name: "My name")
/// ```
ProxyRoutingSettings call({
ProxyRegularTabRoutingMode regularTabsMode,
ProxyConnectionId? regularTabsProxyConnectionId,
ProxyConnectionId? privateTabsProxyConnectionId,
});
}
/// Callable proxy for `copyWith` functionality.
/// Use as `instanceOfProxyRoutingSettings.copyWith(...)` or call `instanceOfProxyRoutingSettings.copyWith.fieldName(value)` for a single field.
class _$ProxyRoutingSettingsCWProxyImpl
implements _$ProxyRoutingSettingsCWProxy {
const _$ProxyRoutingSettingsCWProxyImpl(this._value);
final ProxyRoutingSettings _value;
@override
ProxyRoutingSettings regularTabsMode(
ProxyRegularTabRoutingMode regularTabsMode,
) => call(regularTabsMode: regularTabsMode);
@override
ProxyRoutingSettings regularTabsProxyConnectionId(
ProxyConnectionId? regularTabsProxyConnectionId,
) => call(regularTabsProxyConnectionId: regularTabsProxyConnectionId);
@override
ProxyRoutingSettings privateTabsProxyConnectionId(
ProxyConnectionId? privateTabsProxyConnectionId,
) => call(privateTabsProxyConnectionId: privateTabsProxyConnectionId);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `ProxyRoutingSettings(...).copyWith.fieldName(value)`.
///
/// Example:
/// ```dart
/// ProxyRoutingSettings(...).copyWith(id: 12, name: "My name")
/// ```
ProxyRoutingSettings call({
Object? regularTabsMode = const $CopyWithPlaceholder(),
Object? regularTabsProxyConnectionId = const $CopyWithPlaceholder(),
Object? privateTabsProxyConnectionId = const $CopyWithPlaceholder(),
}) {
return ProxyRoutingSettings(
regularTabsMode:
regularTabsMode == const $CopyWithPlaceholder() ||
regularTabsMode == null
? _value.regularTabsMode
// ignore: cast_nullable_to_non_nullable
: regularTabsMode as ProxyRegularTabRoutingMode,
regularTabsProxyConnectionId:
regularTabsProxyConnectionId == const $CopyWithPlaceholder()
? _value.regularTabsProxyConnectionId
// ignore: cast_nullable_to_non_nullable
: regularTabsProxyConnectionId as ProxyConnectionId?,
privateTabsProxyConnectionId:
privateTabsProxyConnectionId == const $CopyWithPlaceholder()
? _value.privateTabsProxyConnectionId
// ignore: cast_nullable_to_non_nullable
: privateTabsProxyConnectionId as ProxyConnectionId?,
);
}
}
extension $ProxyRoutingSettingsCopyWith on ProxyRoutingSettings {
/// Returns a callable class used to build a new instance with modified fields.
/// Example: `instanceOfProxyRoutingSettings.copyWith(...)` or `instanceOfProxyRoutingSettings.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$ProxyRoutingSettingsCWProxy get copyWith =>
_$ProxyRoutingSettingsCWProxyImpl(this);
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProxyRoutingSettings _$ProxyRoutingSettingsFromJson(
Map<String, dynamic> json,
) => ProxyRoutingSettings.withDefaults(
regularTabsMode: $enumDecodeNullable(
_$ProxyRegularTabRoutingModeEnumMap,
json['regularTabsMode'],
),
regularTabsProxyConnectionId: _proxyConnectionIdFromJson(
json['regularTabsProxyConnectionId'] as String?,
),
privateTabsProxyConnectionId: _proxyConnectionIdFromJson(
json['privateTabsProxyConnectionId'] as String?,
),
);
Map<String, dynamic> _$ProxyRoutingSettingsToJson(
ProxyRoutingSettings instance,
) => <String, dynamic>{
'regularTabsMode':
_$ProxyRegularTabRoutingModeEnumMap[instance.regularTabsMode]!,
'regularTabsProxyConnectionId': _proxyConnectionIdToJson(
instance.regularTabsProxyConnectionId,
),
'privateTabsProxyConnectionId': _proxyConnectionIdToJson(
instance.privateTabsProxyConnectionId,
),
};
const _$ProxyRegularTabRoutingModeEnumMap = {
ProxyRegularTabRoutingMode.container: 'container',
ProxyRegularTabRoutingMode.all: 'all',
};
@@ -25,13 +25,9 @@ part 'tor_settings.g.dart';
enum TorConnectionConfig { auto, direct, obfs4, snowflake }
enum TorRegularTabProxyMode { container, all }
@CopyWith()
@JsonSerializable(includeIfNull: true, constructor: 'withDefaults')
class TorSettings with FastEquatable {
final TorRegularTabProxyMode proxyRegularTabsMode;
final bool proxyPrivateTabsTor;
final TorConnectionConfig config;
final bool requireBridge;
final bool fetchRemoteBridges;
@@ -39,8 +35,6 @@ class TorSettings with FastEquatable {
final String? exitNodeCountry;
TorSettings({
required this.proxyRegularTabsMode,
required this.proxyPrivateTabsTor,
required this.config,
required this.requireBridge,
required this.fetchRemoteBridges,
@@ -49,17 +43,12 @@ class TorSettings with FastEquatable {
});
TorSettings.withDefaults({
TorRegularTabProxyMode? proxyRegularTabsMode,
bool? proxyPrivateTabsTor,
TorConnectionConfig? config,
bool? requireBridge,
bool? fetchRemoteBridges,
this.entryNodeCountry,
this.exitNodeCountry,
}) : proxyRegularTabsMode =
proxyRegularTabsMode ?? TorRegularTabProxyMode.container,
proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
config = config ?? TorConnectionConfig.auto,
}) : config = config ?? TorConnectionConfig.auto,
requireBridge = requireBridge ?? false,
fetchRemoteBridges = fetchRemoteBridges ?? true;
@@ -70,8 +59,6 @@ class TorSettings with FastEquatable {
@override
List<Object?> get hashParameters => [
proxyRegularTabsMode,
proxyPrivateTabsTor,
config,
requireBridge,
fetchRemoteBridges,
@@ -7,10 +7,6 @@ part of 'tor_settings.dart';
// **************************************************************************
abstract class _$TorSettingsCWProxy {
TorSettings proxyRegularTabsMode(TorRegularTabProxyMode proxyRegularTabsMode);
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor);
TorSettings config(TorConnectionConfig config);
TorSettings requireBridge(bool requireBridge);
@@ -29,8 +25,6 @@ abstract class _$TorSettingsCWProxy {
/// TorSettings(...).copyWith(id: 12, name: "My name")
/// ```
TorSettings call({
TorRegularTabProxyMode proxyRegularTabsMode,
bool proxyPrivateTabsTor,
TorConnectionConfig config,
bool requireBridge,
bool fetchRemoteBridges,
@@ -46,15 +40,6 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
final TorSettings _value;
@override
TorSettings proxyRegularTabsMode(
TorRegularTabProxyMode proxyRegularTabsMode,
) => call(proxyRegularTabsMode: proxyRegularTabsMode);
@override
TorSettings proxyPrivateTabsTor(bool proxyPrivateTabsTor) =>
call(proxyPrivateTabsTor: proxyPrivateTabsTor);
@override
TorSettings config(TorConnectionConfig config) => call(config: config);
@@ -83,8 +68,6 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
/// TorSettings(...).copyWith(id: 12, name: "My name")
/// ```
TorSettings call({
Object? proxyRegularTabsMode = const $CopyWithPlaceholder(),
Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(),
Object? config = const $CopyWithPlaceholder(),
Object? requireBridge = const $CopyWithPlaceholder(),
Object? fetchRemoteBridges = const $CopyWithPlaceholder(),
@@ -92,18 +75,6 @@ class _$TorSettingsCWProxyImpl implements _$TorSettingsCWProxy {
Object? exitNodeCountry = const $CopyWithPlaceholder(),
}) {
return TorSettings(
proxyRegularTabsMode:
proxyRegularTabsMode == const $CopyWithPlaceholder() ||
proxyRegularTabsMode == null
? _value.proxyRegularTabsMode
// ignore: cast_nullable_to_non_nullable
: proxyRegularTabsMode as TorRegularTabProxyMode,
proxyPrivateTabsTor:
proxyPrivateTabsTor == const $CopyWithPlaceholder() ||
proxyPrivateTabsTor == null
? _value.proxyPrivateTabsTor
// ignore: cast_nullable_to_non_nullable
: proxyPrivateTabsTor as bool,
config: config == const $CopyWithPlaceholder() || config == null
? _value.config
// ignore: cast_nullable_to_non_nullable
@@ -144,11 +115,6 @@ extension $TorSettingsCopyWith on TorSettings {
TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
TorSettings.withDefaults(
proxyRegularTabsMode: $enumDecodeNullable(
_$TorRegularTabProxyModeEnumMap,
json['proxyRegularTabsMode'],
),
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
config: $enumDecodeNullable(_$TorConnectionConfigEnumMap, json['config']),
requireBridge: json['requireBridge'] as bool?,
fetchRemoteBridges: json['fetchRemoteBridges'] as bool?,
@@ -158,9 +124,6 @@ TorSettings _$TorSettingsFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$TorSettingsToJson(TorSettings instance) =>
<String, dynamic>{
'proxyRegularTabsMode':
_$TorRegularTabProxyModeEnumMap[instance.proxyRegularTabsMode]!,
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
'config': _$TorConnectionConfigEnumMap[instance.config]!,
'requireBridge': instance.requireBridge,
'fetchRemoteBridges': instance.fetchRemoteBridges,
@@ -168,11 +131,6 @@ Map<String, dynamic> _$TorSettingsToJson(TorSettings instance) =>
'exitNodeCountry': instance.exitNodeCountry,
};
const _$TorRegularTabProxyModeEnumMap = {
TorRegularTabProxyMode.container: 'container',
TorRegularTabProxyMode.all: 'all',
};
const _$TorConnectionConfigEnumMap = {
TorConnectionConfig.auto: 'auto',
TorConnectionConfig.direct: 'direct',