diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs index 886a1971..1b681661 100644 --- a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -47,6 +47,7 @@ public class CoreConfigContextBuilder ProtectDomainList = [], RawDnsItem = await AppManager.Instance.GetDNSItem(coreType), RoutingItem = await ConfigHandler.GetDefaultRouting(config), + IsWindows = Utils.IsWindows(), }; var validatorResult = NodeValidatorResult.Empty(); var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node); diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index 5c6d7028..6fa8e65b 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -41,7 +41,6 @@ public static class ConfigHandler Loglevel = "warning", MuxEnabled = false, }; - config.CoreBasicItem.SendThrough = config.CoreBasicItem.SendThrough?.TrimEx(); if (config.Inbound == null) { diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index e54f1a7a..b23a8fc3 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -17,6 +17,8 @@ public class CoreBasicItem public string? SendThrough { get; set; } + public string? BindInterface { get; set; } + public bool EnableFragment { get; set; } public bool EnableCacheFile4Sbox { get; set; } = true; diff --git a/v2rayN/ServiceLib/Models/CoreConfigContext.cs b/v2rayN/ServiceLib/Models/CoreConfigContext.cs index 8123f19f..e85d5385 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigContext.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigContext.cs @@ -17,4 +17,6 @@ public record CoreConfigContext // TUN Compatibility public bool IsTunEnabled { get; init; } = false; public HashSet ProtectDomainList { get; init; } = []; + + public bool IsWindows { get; init; } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 688d7e6c..a948dee9 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -3681,6 +3681,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Bind Interface 的本地化字符串。 + /// + public static string TbSettingsBindInterface { + get { + return ResourceManager.GetString("TbSettingsBindInterface", resourceCulture); + } + } + + /// + /// 查找类似 For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems and TUN mode 的本地化字符串。 + /// + public static string TbSettingsBindInterfaceTip { + get { + return ResourceManager.GetString("TbSettingsBindInterfaceTip", resourceCulture); + } + } + /// /// 查找类似 Users in China region can ignore this item 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index f2818543..c3f53c89 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1689,9 +1689,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Legacy TUN Protect - - For multi-interface environments, enter the local machine's IPv4 address - Camouflage domain @@ -1713,4 +1710,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if UDP Test Url + + Local outbound address (SendThrough) + + + For multi-interface environments, enter the local machine's IPv4 address + + + Please fill in the correct IPv4 address for SendThrough. + + + Bind Interface + + + For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems and TUN mode + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 1a782f98..2a5d8823 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1311,15 +1311,6 @@ Le mot de passe sera vérifié en ligne de commande. En cas d’échec ou de dysfonctionnement, redémarrez l’application. Il n’est pas stocké et doit être saisi à chaque redémarrage. - - Adresse sortante locale (SendThrough) - - - Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale. - - - Veuillez saisir l’adresse IPv4 correcte de SendThrough. - Mode XHTTP @@ -1710,4 +1701,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Only for fetching self-signed certificates. This may expose you to MITM risks. + + Adresse sortante locale (SendThrough) + + + Pour environnements multi-interfaces, entrez l'adresse IPv4 de la machine locale + + + Veuillez saisir l’adresse IPv4 correcte de SendThrough. + + + Lier l'interface + + + Pour les environnements multi-interfaces, entrez le nom de l'interface à lier. Ne fonctionne que sur les systèmes Windows et en mode TUN + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index dbd24707..33656ba8 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1689,9 +1689,6 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Legacy TUN Protect - - For multi-interface environments, enter the local machine's IPv4 address - Álcázási tartomány @@ -1713,4 +1710,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if UDP Test Url + + Local outbound address (SendThrough) + + + For multi-interface environments, enter the local machine's IPv4 address + + + Please fill in the correct IPv4 address for SendThrough. + + + Bind Interface + + + For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems and TUN mode + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 6ceef3c1..9659926b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1314,15 +1314,6 @@ The password will be validated via the command line. If a validation error causes the application to malfunction, please restart the application. The password will not be stored and must be entered again after each restart. - - Local outbound address (SendThrough) - - - For multi-interface environments, enter the local machine's IPv4 address - - - Please fill in the correct IPv4 address for SendThrough. - xhttp mode @@ -1719,4 +1710,19 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if UDP Test Url - + + Local outbound address (SendThrough) + + + For multi-interface environments, enter the local machine's IPv4 address + + + Please fill in the correct IPv4 address for SendThrough. + + + Bind Interface + + + For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems and TUN mode + + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 8f234843..07e0e841 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1689,9 +1689,6 @@ Устаревшая защита TUN (Legacy Protect) - - Для среды с несколькими сетевыми интерфейсами укажите IPv4-адрес локального компьютера - Камуфляжный домен @@ -1713,10 +1710,19 @@ URL для UDP-теста - - Укажите корректный IPv4-адрес для SendThrough. - Локальный исходящий адрес (SendThrough) - + + Для среды с несколькими сетевыми интерфейсами укажите IPv4-адрес локального компьютера + + + Укажите корректный IPv4-адрес для SendThrough. + + + Привязать интерфейс + + + Для среды с несколькими сетевыми интерфейсами укажите имя интерфейса для привязки. Работает только в Windows и режиме TUN + + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 759596e9..f1d0406f 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1311,15 +1311,6 @@ 密码将调用命令行校验,如果因为校验错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。 - - 本地出站地址 (SendThrough) - - - 用于多网口环境,请填写本机 IPv4 地址 - - - 请填写正确的 SendThrough IPv4 地址。 - XHTTP 模式 @@ -1716,4 +1707,19 @@ UDP 测试地址 - + + 本地出站地址 (SendThrough) + + + 用于多网口环境,请填写本机 IPv4 地址 + + + 请填写正确的 SendThrough IPv4 地址。 + + + 绑定网口 + + + 用于多网口环境,填写要绑定的网口名称,仅生效于 Windows 系统和 TUN 模式 + + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 17ee910a..4dd3ea86 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1686,9 +1686,6 @@ Legacy TUN Protect - - For multi-interface environments, enter the local machine's IPv4 address - 偽裝域名 @@ -1710,4 +1707,19 @@ UDP Test Url + + Local outbound address (SendThrough) + + + For multi-interface environments, enter the local machine's IPv4 address + + + Please fill in the correct IPv4 address for SendThrough. + + + Bind Interface + + + For multi-interface environments, enter the name of the interface to bind. Only effective on Windows systems and TUN mode + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index fe00861d..9cddbfe8 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -57,6 +57,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context) ConvertGeo2Ruleset(); + ApplyOutboundBindInterface(); ApplyOutboundSendThrough(); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); @@ -170,6 +171,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context) _coreConfig.route.rules.Add(rule); } + ApplyOutboundBindInterface(); ApplyOutboundSendThrough(); ret.Success = true; ret.Data = JsonUtils.Serialize(_coreConfig); @@ -229,6 +231,7 @@ public partial class CoreConfigSingboxService(CoreConfigContext context) listen_port = port, type = EInboundProtocol.mixed.ToString(), }); + ApplyOutboundBindInterface(); ApplyOutboundSendThrough(); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs index d20aec1c..c081fbe3 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs @@ -59,22 +59,38 @@ public partial class CoreConfigSingboxService return JsonUtils.Serialize(fullConfigTemplateNode); } - private void ApplyOutboundSendThrough() + private void ApplyOutboundBindInterface() { - var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx(); + var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx(); + if (bindInterface.IsNullOrEmpty()) + { + return; + } + if (!(context.IsTunEnabled || context.IsWindows)) + { + return; + } foreach (var outbound in _coreConfig.outbounds ?? []) { - outbound.inet4_bind_address = ShouldApplySendThrough(outbound, sendThrough) ? sendThrough : null; + outbound.detour = ShouldBindNet(outbound) ? bindInterface : null; } } - private static bool ShouldApplySendThrough(Outbound4Sbox outbound, string? sendThrough) + private void ApplyOutboundSendThrough() { + var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx(); if (sendThrough.IsNullOrEmpty()) { - return false; + return; } + foreach (var outbound in _coreConfig.outbounds ?? []) + { + outbound.inet4_bind_address = ShouldBindNet(outbound) ? sendThrough : null; + } + } + private static bool ShouldBindNet(Outbound4Sbox outbound) + { if (outbound.type is "direct" or "block" or "dns" or "selector" or "urltest") { return false; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index 667f7818..85963bb4 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -60,6 +60,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) { ApplyOutboundFragment(); } + ApplyOutboundBindInterface(); ApplyOutboundSendThrough(); var finalRule = BuildFinalRule(); @@ -203,6 +204,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) { ApplyOutboundFragment(); } + ApplyOutboundBindInterface(); ApplyOutboundSendThrough(); //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); ret.Success = true; @@ -274,6 +276,7 @@ public partial class CoreConfigV2rayService(CoreConfigContext context) { ApplyOutboundFragment(); } + ApplyOutboundBindInterface(); ApplyOutboundSendThrough(); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs index e6bc48cb..a218ac13 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs @@ -134,22 +134,59 @@ public partial class CoreConfigV2rayService return JsonUtils.Serialize(fullConfigTemplateNode); } - private void ApplyOutboundSendThrough() + private void ApplyOutboundBindInterface() { - var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx(); + var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx(); + if (bindInterface.IsNullOrEmpty()) + { + return; + } + if (!(context.IsTunEnabled || context.IsWindows)) + { + return; + } foreach (var outbound in _coreConfig.outbounds ?? []) { - outbound.sendThrough = ShouldApplySendThrough(outbound, sendThrough) ? sendThrough : null; + if (!ShouldBindNet(outbound)) + { + continue; + } + outbound.streamSettings ??= new(); + outbound.streamSettings.sockopt ??= new(); + outbound.streamSettings.sockopt.Interface = bindInterface; + // xhttp download bind interface + if (outbound?.streamSettings?.xhttpSettings?.extra is null) + { + continue; + } + var xhttpExtra = JsonUtils.ParseJson(JsonUtils.Serialize(outbound.streamSettings.xhttpSettings!.extra)); + if (xhttpExtra is not JsonObject xhttpExtraObject + || xhttpExtraObject["downloadSettings"] is not JsonObject downloadSettings) + { + continue; + } + var sockopt = downloadSettings["sockopt"] as JsonObject ?? new JsonObject(); + sockopt["interface"] = bindInterface; + downloadSettings["sockopt"] = sockopt; + outbound.streamSettings.xhttpSettings.extra = xhttpExtraObject; } } - private static bool ShouldApplySendThrough(Outbounds4Ray outbound, string? sendThrough) + private void ApplyOutboundSendThrough() { + var sendThrough = _config.CoreBasicItem.SendThrough?.TrimEx(); if (sendThrough.IsNullOrEmpty()) { - return false; + return; } + foreach (var outbound in _coreConfig.outbounds ?? []) + { + outbound.sendThrough = ShouldBindNet(outbound) ? sendThrough : null; + } + } + private static bool ShouldBindNet(Outbounds4Ray outbound) + { if (outbound.protocol is "freedom" or "blackhole" or "dns" or "loopback") { return false; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs index 4831b5bc..9cb02e01 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs @@ -60,6 +60,11 @@ public partial class CoreConfigV2rayService { tunInbound.settings.gateway = ["172.18.0.1/30"]; } + var bindInterface = _config.CoreBasicItem.BindInterface?.TrimEx(); + if (!bindInterface.IsNullOrEmpty()) + { + tunInbound.settings.autoOutboundsInterface = bindInterface; + } tunInbound.sniffing = inbound.sniffing; _coreConfig.inbounds.Add(tunInbound); } diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 51db6809..122772c4 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -21,6 +21,7 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public string defFingerprint { get; set; } [Reactive] public string defUserAgent { get; set; } [Reactive] public string sendThrough { get; set; } + [Reactive] public string bindInterface { get; set; } [Reactive] public string mux4SboxProtocol { get; set; } [Reactive] public bool enableCacheFile4Sbox { get; set; } [Reactive] public int? hyUpMbps { get; set; } @@ -156,7 +157,8 @@ public class OptionSettingViewModel : MyReactiveObject defAllowInsecure = _config.CoreBasicItem.DefAllowInsecure; defFingerprint = _config.CoreBasicItem.DefFingerprint; defUserAgent = _config.CoreBasicItem.DefUserAgent; - sendThrough = _config.CoreBasicItem.SendThrough; + sendThrough = _config.CoreBasicItem.SendThrough ?? string.Empty; + bindInterface = _config.CoreBasicItem.BindInterface ?? string.Empty; mux4SboxProtocol = _config.Mux4SboxItem.Protocol; enableCacheFile4Sbox = _config.CoreBasicItem.EnableCacheFile4Sbox; hyUpMbps = _config.HysteriaItem.UpMbps; @@ -301,7 +303,7 @@ public class OptionSettingViewModel : MyReactiveObject NoticeManager.Instance.Enqueue(ResUI.FillLocalListeningPort); return; } - var sendThroughValue = sendThrough?.TrimEx(); + var sendThroughValue = sendThrough.TrimEx(); if (sendThroughValue.IsNotEmpty() && !Utils.IsIpv4(sendThroughValue)) { NoticeManager.Instance.Enqueue(ResUI.FillCorrectSendThroughIPv4); @@ -346,7 +348,8 @@ public class OptionSettingViewModel : MyReactiveObject _config.CoreBasicItem.DefAllowInsecure = defAllowInsecure; _config.CoreBasicItem.DefFingerprint = defFingerprint; _config.CoreBasicItem.DefUserAgent = defUserAgent; - _config.CoreBasicItem.SendThrough = sendThrough?.TrimEx(); + _config.CoreBasicItem.SendThrough = sendThrough.TrimEx(); + _config.CoreBasicItem.BindInterface = bindInterface.TrimEx(); _config.Mux4SboxItem.Protocol = mux4SboxProtocol; _config.CoreBasicItem.EnableCacheFile4Sbox = enableCacheFile4Sbox; _config.HysteriaItem.UpMbps = hyUpMbps ?? 0; diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index 50c938fa..c7833fe9 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -37,8 +37,8 @@ + ColumnDefinitions="Auto,Auto,*" + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + + + + + @@ -398,21 +399,44 @@ Margin="{StaticResource Margin8}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" - Text="{x:Static resx:ResUI.TbSettingsSendThrough}" /> + Text="{x:Static resx:ResUI.TbSettingsBindInterface}" /> + Style="{StaticResource DefTextBox}" /> + + + + diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs index 59254155..8092ea6c 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -75,6 +75,7 @@ public partial class OptionSettingWindow this.Bind(ViewModel, vm => vm.defFingerprint, v => v.cmbdefFingerprint.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.defUserAgent, v => v.cmbdefUserAgent.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.sendThrough, v => v.txtsendThrough.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.bindInterface, v => v.txtbindInterface.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.mux4SboxProtocol, v => v.cmbmux4SboxProtocol.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.enableCacheFile4Sbox, v => v.togenableCacheFile4Sbox.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.hyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);