diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index bdd9f99..ab2a561 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -758,5 +758,7 @@ "invalidTime": "Invalid time", "removeDomain": "Remove domain", "addDomain": "Add domain", - "notLess1Hour": "Time cannot be less than 1 hour" + "notLess1Hour": "Time cannot be less than 1 hour", + "subnetPrefixLengthIpv4": "Subnet prefix length for IPv4", + "subnetPrefixLengthIpv6": "Subnet prefix length for IPv6" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index d9b2f9a..6a701d1 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -758,5 +758,7 @@ "invalidTime": "Tiempo no válido", "removeDomain": "Eliminar dominio", "addDomain": "Añadir dominio", - "notLess1Hour": "El tiempo no puede ser inferior a 1 hora" + "notLess1Hour": "El tiempo no puede ser inferior a 1 hora", + "subnetPrefixLengthIpv4": "Longitud del prefijo de subred para IPv4", + "subnetPrefixLengthIpv6": "Longitud del prefijo de subred para IPv6" } \ No newline at end of file diff --git a/lib/models/dns_info.dart b/lib/models/dns_info.dart index c0cabd6..d4152c8 100644 --- a/lib/models/dns_info.dart +++ b/lib/models/dns_info.dart @@ -23,6 +23,8 @@ class DnsInfo { String blockingIpv6; List defaultLocalPtrUpstreams; int? blockedResponseTtl; + int? ratelimitSubnetLenIpv4; + int? ratelimitSubnetLenIpv6; DnsInfo({ required this.upstreamDns, @@ -49,6 +51,8 @@ class DnsInfo { required this.blockingIpv6, required this.defaultLocalPtrUpstreams, required this.blockedResponseTtl, + required this.ratelimitSubnetLenIpv4, + required this.ratelimitSubnetLenIpv6, }); factory DnsInfo.fromJson(Map json) => DnsInfo( @@ -75,7 +79,9 @@ class DnsInfo { blockingIpv4: json["blocking_ipv4"], blockingIpv6: json["blocking_ipv6"], defaultLocalPtrUpstreams: json["default_local_ptr_upstreams"] != null ? List.from(json["default_local_ptr_upstreams"].map((x) => x)) : [], - blockedResponseTtl: json["blocked_response_ttl"] + blockedResponseTtl: json["blocked_response_ttl"], + ratelimitSubnetLenIpv4: json["ratelimit_subnet_len_ipv4"], + ratelimitSubnetLenIpv6: json["ratelimit_subnet_len_ipv6"], ); Map toJson() => { @@ -102,6 +108,8 @@ class DnsInfo { "blocking_ipv4": blockingIpv4, "blocking_ipv6": blockingIpv6, "default_local_ptr_upstreams": List.from(defaultLocalPtrUpstreams.map((x) => x)), - "blocked_response_ttl": blockedResponseTtl + "blocked_response_ttl": blockedResponseTtl, + "ratelimit_subnet_len_ipv4": ratelimitSubnetLenIpv4, + "ratelimit_subnet_len_ipv6": ratelimitSubnetLenIpv6, }; } diff --git a/lib/providers/dns_provider.dart b/lib/providers/dns_provider.dart index 84b86cd..9c8d705 100644 --- a/lib/providers/dns_provider.dart +++ b/lib/providers/dns_provider.dart @@ -162,6 +162,8 @@ class DnsProvider with ChangeNotifier { data.blockingIpv4 = value['blocking_ipv4']; data.blockingIpv6 = value['blocking_ipv6']; data.blockedResponseTtl = value['blocked_response_ttl']; + data.ratelimitSubnetLenIpv4 = value['ratelimit_subnet_len_ipv4']; + data.ratelimitSubnetLenIpv6 = value['ratelimit_subnet_len_ipv6']; setDnsInfoData(data); return result; } diff --git a/lib/screens/settings/dns/dns_server_settings.dart b/lib/screens/settings/dns/dns_server_settings.dart index b13f593..6e747f3 100644 --- a/lib/screens/settings/dns/dns_server_settings.dart +++ b/lib/screens/settings/dns/dns_server_settings.dart @@ -23,49 +23,49 @@ class DnsServerSettingsScreen extends StatefulWidget { } class _DnsServerSettingsScreenState extends State { - final TextEditingController limitRequestsController = TextEditingController(); - String? limitRequestsError; + final _limitRequestsController = TextEditingController(); + final _ipv4PrefixSubnetController = TextEditingController(); + String? _ipv4PrefixSubnetError; + final _ipv6PrefixSubnetController = TextEditingController(); + String? _ipv6PrefixSubnetError; + String? _limitRequestsError; final _expandableCustomEdns = ExpandableController(); final _expandableEdnsIp = ExpandableController(); - bool enableEdns = false; - bool useCustomIpEdns = false; + bool _enableEdns = false; + bool _useCustomIpEdns = false; final _customIpEdnsController = TextEditingController(); - String? ednsIpError; - bool enableDnssec = false; - bool disableIpv6Resolving = false; + String? _ednsIpError; + bool _enableDnssec = false; + bool _disableIpv6Resolving = false; String blockingMode = "default"; - final TextEditingController ipv4controller = TextEditingController(); - String? ipv4error; - final TextEditingController ipv6controller = TextEditingController(); + final _ipv4controller = TextEditingController(); + String? _ipv4error; + final _ipv6controller = TextEditingController(); String? ipv6error; final _ttlController = TextEditingController(); String? _ttlError; - bool isDataValid = false; - void validateIpv4(String value) { RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$'); if (ipAddress.hasMatch(value) == true) { - setState(() => ipv4error = null); + setState(() => _ipv4error = null); } else { - setState(() => ipv4error = AppLocalizations.of(context)!.invalidIp); + setState(() => _ipv4error = AppLocalizations.of(context)!.invalidIp); } - validateData(); } void validateEdns(String value) { RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$'); if (ipAddress.hasMatch(value) == true) { - setState(() => ednsIpError = null); + setState(() => _ednsIpError = null); } else { - setState(() => ednsIpError = AppLocalizations.of(context)!.ipNotValid); + setState(() => _ednsIpError = AppLocalizations.of(context)!.ipNotValid); } - validateData(); } void validateIpv6(String value) { @@ -76,63 +76,77 @@ class _DnsServerSettingsScreenState extends State { else { setState(() => ipv6error = AppLocalizations.of(context)!.invalidIp); } - validateData(); } - void validateData() { + bool validateData() { if ( - limitRequestsController.text != '' && - limitRequestsError == null && + (_limitRequestsController.text == "" || (_limitRequestsController.text != "" && _limitRequestsError == null)) && ( blockingMode != 'custom_ip' || ( blockingMode == 'custom_ip' && - ipv4controller.text != '' && - ipv4error == null && - ipv6controller.text != '' && + _ipv4controller.text != '' && + _ipv4error == null && + _ipv6controller.text != '' && ipv6error == null ) ) == true && - ednsIpError == null && - _ttlError == null + _ednsIpError == null && + _ttlController.text != "" && _ttlError == null && + (_ipv4PrefixSubnetController.text == "" || (_ipv4PrefixSubnetController.text != "" && _ipv4PrefixSubnetError == null)) && + (_ipv6PrefixSubnetController.text == "" || (_ipv6PrefixSubnetController.text != "" && _ipv6PrefixSubnetError == null)) ) { - setState(() => isDataValid = true); + return true; } else { - setState(() => isDataValid = false); + return false; } } - void validateNumber(String value) { - final regex = RegExp(r'^(\d)+$'); - if (regex.hasMatch(value) == true) { - setState(() => _ttlError = null); + String? validateTtl(String value) { + if (value == "") return AppLocalizations.of(context)!.valueNotNumber; + if (int.tryParse(value) != null) { + return null; } else { - setState(() => _ttlError = AppLocalizations.of(context)!.invalidValue); + return AppLocalizations.of(context)!.valueNotNumber; + } + } + + String? validateNumber(String value) { + if (value == "") return null; + if (int.tryParse(value) != null) { + return null; + } + else { + return AppLocalizations.of(context)!.valueNotNumber; } - validateData(); } @override void initState() { final dnsProvider = Provider.of(context, listen: false); - limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString(); - enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled; - useCustomIpEdns = dnsProvider.dnsInfo!.ednsCsUseCustom ?? false; + _limitRequestsController.text = dnsProvider.dnsInfo!.ratelimit.toString(); + _enableEdns = dnsProvider.dnsInfo!.ednsCsEnabled; + _useCustomIpEdns = dnsProvider.dnsInfo!.ednsCsUseCustom ?? false; _customIpEdnsController.text = dnsProvider.dnsInfo!.ednsCsCustomIp ?? ""; if (dnsProvider.dnsInfo!.ednsCsEnabled == true) _expandableCustomEdns.toggle(); if (dnsProvider.dnsInfo!.ednsCsUseCustom == true) _expandableEdnsIp.toggle(); - enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled; - disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6; + _enableDnssec = dnsProvider.dnsInfo!.dnssecEnabled; + _disableIpv6Resolving = dnsProvider.dnsInfo!.disableIpv6; blockingMode = dnsProvider.dnsInfo!.blockingMode; - ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4; - ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6; - isDataValid = true; + _ipv4controller.text = dnsProvider.dnsInfo!.blockingIpv4; + _ipv6controller.text = dnsProvider.dnsInfo!.blockingIpv6; _ttlController.text = dnsProvider.dnsInfo!.blockedResponseTtl != null ? dnsProvider.dnsInfo!.blockedResponseTtl.toString() : ""; + _ipv4PrefixSubnetController.text = dnsProvider.dnsInfo!.ratelimitSubnetLenIpv4 != null + ? dnsProvider.dnsInfo!.ratelimitSubnetLenIpv4.toString() + : ""; + _ipv6PrefixSubnetController.text = dnsProvider.dnsInfo!.ratelimitSubnetLenIpv6 != null + ? dnsProvider.dnsInfo!.ratelimitSubnetLenIpv6.toString() + : ""; super.initState(); } @@ -147,16 +161,18 @@ class _DnsServerSettingsScreenState extends State { processModal.open(AppLocalizations.of(context)!.savingConfig); final result = await dnsProvider.saveDnsServerConfig({ - "ratelimit": int.parse(limitRequestsController.text), - "edns_cs_enabled": enableEdns, - "edns_cs_use_custom": useCustomIpEdns, + "ratelimit": int.parse(_limitRequestsController.text), + "edns_cs_enabled": _enableEdns, + "edns_cs_use_custom": _useCustomIpEdns, "edns_cs_custom_ip": _customIpEdnsController.text, - "dnssec_enabled": enableDnssec, - "disable_ipv6": disableIpv6Resolving, + "dnssec_enabled": _enableDnssec, + "disable_ipv6": _disableIpv6Resolving, "blocking_mode": blockingMode, - "blocking_ipv4": ipv4controller.text, - "blocking_ipv6": ipv6controller.text, - "blocked_response_ttl": int.tryParse(_ttlController.text) + "blocking_ipv4": _ipv4controller.text, + "blocking_ipv6": _ipv6controller.text, + "blocked_response_ttl": int.tryParse(_ttlController.text), + "ratelimit_subnet_len_ipv4": int.tryParse(_ipv4PrefixSubnetController.text), + "ratelimit_subnet_len_ipv6": int.tryParse(_ipv6PrefixSubnetController.text), }); processModal.close(); @@ -186,22 +202,24 @@ class _DnsServerSettingsScreenState extends State { void updateBlockingMode(String mode) { if (mode != 'custom_ip') { - ipv4controller.text = ''; - ipv4error = null; - ipv6controller.text = ''; + _ipv4controller.text = ''; + _ipv4error = null; + _ipv6controller.text = ''; ipv6error = null; } setState(() => blockingMode = mode); validateData(); } + final dataValid = validateData(); + return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.dnsServerSettings), surfaceTintColor: isDesktop(width) ? Colors.transparent : null, actions: [ IconButton( - onPressed: isDataValid == true + onPressed: dataValid == true ? () => saveData() : null, icon: const Icon(Icons.save_rounded), @@ -217,16 +235,8 @@ class _DnsServerSettingsScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextFormField( - controller: limitRequestsController, - onChanged: (value) { - if (int.tryParse(value) != null) { - setState(() => limitRequestsError = null); - } - else { - setState(() => limitRequestsError = AppLocalizations.of(context)!.valueNotNumber); - } - validateData(); - }, + controller: _limitRequestsController, + onChanged: (v) => setState(() => _limitRequestsError = validateNumber(v)), decoration: InputDecoration( prefixIcon: const Icon(Icons.looks_one_rounded), border: const OutlineInputBorder( @@ -235,22 +245,58 @@ class _DnsServerSettingsScreenState extends State { ) ), labelText: AppLocalizations.of(context)!.limitRequestsSecond, - errorText: limitRequestsError + errorText: _limitRequestsError ), keyboardType: TextInputType.number, ), ), - const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + child: TextFormField( + controller: _ipv4PrefixSubnetController, + onChanged: (v) => setState(() => _ipv4PrefixSubnetError = validateNumber(v)), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.subnetPrefixLengthIpv4, + errorText: _ipv4PrefixSubnetError + ), + keyboardType: TextInputType.number, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: TextFormField( + controller: _ipv6PrefixSubnetController, + onChanged: (v) => setState(() => _ipv6PrefixSubnetError = validateNumber(v)), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.skip_previous_rounded), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(10) + ) + ), + labelText: AppLocalizations.of(context)!.subnetPrefixLengthIpv6, + errorText: _ipv6PrefixSubnetError + ), + keyboardType: TextInputType.number, + ), + ), + const SizedBox(height: 16), CustomSwitchListTile( - value: enableEdns, + value: _enableEdns, onChanged: (value) => setState(() { - enableEdns = value; + _enableEdns = value; _expandableCustomEdns.toggle(); if (value == false) { - useCustomIpEdns = false; + _useCustomIpEdns = false; if (_expandableEdnsIp.expanded == true) _expandableEdnsIp.toggle(); _customIpEdnsController.text = ""; - ednsIpError = null; + _ednsIpError = null; } validateData(); }), @@ -270,13 +316,13 @@ class _DnsServerSettingsScreenState extends State { bottom: 12, right: 16 ), - value: useCustomIpEdns, + value: _useCustomIpEdns, onChanged: (value) => setState(() { - useCustomIpEdns = value; + _useCustomIpEdns = value; _expandableEdnsIp.toggle(); - if (useCustomIpEdns == false) { + if (_useCustomIpEdns == false) { _customIpEdnsController.text = ""; - ednsIpError = null; + _ednsIpError = null; } validateData(); }), @@ -304,7 +350,7 @@ class _DnsServerSettingsScreenState extends State { Radius.circular(10) ) ), - errorText: ednsIpError, + errorText: _ednsIpError, labelText: AppLocalizations.of(context)!.ipAddress, ), ), @@ -316,14 +362,14 @@ class _DnsServerSettingsScreenState extends State { ) ), CustomSwitchListTile( - value: enableDnssec, - onChanged: (value) => setState(() => enableDnssec = value), + value: _enableDnssec, + onChanged: (value) => setState(() => _enableDnssec = value), title: AppLocalizations.of(context)!.enableDnssec, subtitle: AppLocalizations.of(context)!.enableDnssecDescription, ), CustomSwitchListTile( - value: disableIpv6Resolving, - onChanged: (value) => setState(() => disableIpv6Resolving = value), + value: _disableIpv6Resolving, + onChanged: (value) => setState(() => _disableIpv6Resolving = value), title: AppLocalizations.of(context)!.disableResolvingIpv6, subtitle: AppLocalizations.of(context)!.disableResolvingIpv6Description, ), @@ -373,7 +419,7 @@ class _DnsServerSettingsScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: TextFormField( - controller: ipv4controller, + controller: _ipv4controller, onChanged: validateIpv4, decoration: InputDecoration( prefixIcon: const Icon(Icons.link_rounded), @@ -382,7 +428,7 @@ class _DnsServerSettingsScreenState extends State { Radius.circular(10) ) ), - errorText: ipv4error, + errorText: _ipv4error, helperText: AppLocalizations.of(context)!.blockingIpv4Description, helperMaxLines: 10, labelText: AppLocalizations.of(context)!.blockingIpv4, @@ -394,7 +440,7 @@ class _DnsServerSettingsScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: TextFormField( - controller: ipv6controller, + controller: _ipv6controller, onChanged: validateIpv6, decoration: InputDecoration( prefixIcon: const Icon(Icons.link_rounded), @@ -417,7 +463,7 @@ class _DnsServerSettingsScreenState extends State { padding: const EdgeInsets.all(16), child: TextFormField( controller: _ttlController, - onChanged: validateNumber, + onChanged: (v) => setState(() => _ttlError = validateTtl(v)), decoration: InputDecoration( prefixIcon: const Icon(Icons.timer_rounded), border: const OutlineInputBorder(