Tail Fragmentation (#9476)

This commit is contained in:
DHR60
2026-06-05 11:45:49 +00:00
committed by GitHub
parent 0d5169e4cd
commit 27b8f889fe
14 changed files with 192 additions and 40 deletions
@@ -17,6 +17,8 @@ public class CoreBasicItem
public bool EnableFragment { get; set; } public bool EnableFragment { get; set; }
public bool EnableFinalFragment { get; set; }
public bool EnableCacheFile4Sbox { get; set; } = true; public bool EnableCacheFile4Sbox { get; set; } = true;
} }
@@ -93,6 +93,7 @@ public class Rule4Sbox
public List<string>? wifi_bssid { get; set; } public List<string>? wifi_bssid { get; set; }
public bool? rule_set_ip_cidr_match_source { get; set; } public bool? rule_set_ip_cidr_match_source { get; set; }
public bool? rule_set_ip_cidr_accept_empty { get; set; } public bool? rule_set_ip_cidr_accept_empty { get; set; }
public bool? tls_record_fragment { get; set; }
} }
[Serializable] [Serializable]
@@ -489,6 +489,7 @@ public class UdpHop4Ray
public class Finalmask4Ray public class Finalmask4Ray
{ {
public List<Mask4Ray>? tcp { get; set; }
public List<Mask4Ray>? udp { get; set; } public List<Mask4Ray>? udp { get; set; }
public QuicParams4Ray? quicParams { get; set; } public QuicParams4Ray? quicParams { get; set; }
} }
+18
View File
@@ -3033,6 +3033,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Enable Final Fragment 的本地化字符串。
/// </summary>
public static string TbEnableFinalFragment {
get {
return ResourceManager.GetString("TbEnableFinalFragment", resourceCulture);
}
}
/// <summary>
/// 查找类似 Split the tail of packets into smaller fragments. This may affect throughput and latency. 的本地化字符串。
/// </summary>
public static string TbEnableFinalFragmentTip {
get {
return ResourceManager.GetString("TbEnableFinalFragmentTip", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Enable Tun 的本地化字符串。 /// 查找类似 Enable Tun 的本地化字符串。
/// </summary> /// </summary>
+6
View File
@@ -1764,4 +1764,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="MsgOptionsConflict" xml:space="preserve"> <data name="MsgOptionsConflict" xml:space="preserve">
<value>Conflict between {0} and {1}</value> <value>Conflict between {0} and {1}</value>
</data> </data>
<data name="TbEnableFinalFragment" xml:space="preserve">
<value>Enable Final Fragment</value>
</data>
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
<value>Split the tail of packets into smaller fragments. This may affect throughput and latency.</value>
</data>
</root> </root>
@@ -1761,4 +1761,10 @@
<data name="MsgOptionsConflict" xml:space="preserve"> <data name="MsgOptionsConflict" xml:space="preserve">
<value>{0} 与 {1} 冲突</value> <value>{0} 与 {1} 冲突</value>
</data> </data>
<data name="TbEnableFinalFragment" xml:space="preserve">
<value>启用末端分片</value>
</data>
<data name="TbEnableFinalFragmentTip" xml:space="preserve">
<value>将数据包末端拆分为更小片段发送,可能影响吞吐与延迟。</value>
</data>
</root> </root>
@@ -96,6 +96,15 @@ public partial class CoreConfigSingboxService
new() { protocol = ["dns"] }, new() { protocol = ["dns"] },
], ],
}); });
if (_config.CoreBasicItem.EnableFinalFragment)
{
_coreConfig.route.rules.Add(new()
{
protocol = ["tls"],
action = "route-options",
tls_record_fragment = true,
});
}
} }
else else
{ {
@@ -104,6 +113,14 @@ public partial class CoreConfigSingboxService
port = [53], port = [53],
action = "hijack-dns", action = "hijack-dns",
}); });
if (_config.CoreBasicItem.EnableFinalFragment)
{
_coreConfig.route.rules.Add(new()
{
action = "route-options",
tls_record_fragment = true,
});
}
} }
var hostsDomains = new List<string>(); var hostsDomains = new List<string>();
@@ -60,6 +60,10 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
ApplyOutboundFragment(); ApplyOutboundFragment();
} }
if (_config.CoreBasicItem.EnableFinalFragment)
{
ApplyFinalFragment();
}
ApplyOutboundBindInterface(); ApplyOutboundBindInterface();
ApplyOutboundSendThrough(); ApplyOutboundSendThrough();
@@ -204,6 +208,10 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
ApplyOutboundFragment(); ApplyOutboundFragment();
} }
if (_config.CoreBasicItem.EnableFinalFragment)
{
ApplyFinalFragment();
}
ApplyOutboundBindInterface(); ApplyOutboundBindInterface();
ApplyOutboundSendThrough(); ApplyOutboundSendThrough();
//ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary());
@@ -276,6 +284,10 @@ public partial class CoreConfigV2rayService(CoreConfigContext context)
{ {
ApplyOutboundFragment(); ApplyOutboundFragment();
} }
if (_config.CoreBasicItem.EnableFinalFragment)
{
ApplyFinalFragment();
}
ApplyOutboundBindInterface(); ApplyOutboundBindInterface();
ApplyOutboundSendThrough(); ApplyOutboundSendThrough();
@@ -826,29 +826,7 @@ public partial class CoreConfigV2rayService
&& (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) && (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true))
.ToList(); .ToList();
var configPackets = _config.Fragment4RayItem?.Packets ?? "tlshello"; var (fragmentMask, noiseMask) = BuildFragmentsMasks();
var configLength = _config.Fragment4RayItem?.Length ?? "50-100";
var configDelay = _config.Fragment4RayItem?.Interval ?? "10-20";
var fragmentMask = new Mask4Ray
{
type = "fragment",
settings = new MaskSettings4Ray
{
packets = configPackets,
length = configLength,
delay = configDelay,
}
};
var noiseMask = new Mask4Ray
{
type = "noise",
settings = new MaskSettings4Ray
{
length = "10-20",
delay = "10-16",
}
};
foreach (var outbound in actOutboundWithTlsList) foreach (var outbound in actOutboundWithTlsList)
{ {
@@ -882,4 +860,67 @@ public partial class CoreConfigV2rayService
outbound.streamSettings.finalmask = finalMaskJsonObj; outbound.streamSettings.finalmask = finalMaskJsonObj;
} }
} }
private void ApplyFinalFragment()
{
var (fragmentMask, noiseMask) = BuildFragmentsMasks();
var actOutboundList = _coreConfig.outbounds.Where(n => n.tag.StartsWith(Global.ProxyTag)).ToList();
var fragmentFreedom = new Outbounds4Ray()
{
tag = $"{Global.ProxyTag}-fragment-freedom",
protocol = "freedom",
streamSettings = new StreamSettings4Ray
{
finalmask = new Finalmask4Ray
{
tcp = [fragmentMask],
udp = [noiseMask],
}
}
};
foreach (var outbound in actOutboundList)
{
var index = _coreConfig.outbounds.IndexOf(outbound);
var originalTag = outbound.tag;
var afterTag = $"fragment-{originalTag}";
var cloneFragmentFreedom = JsonUtils.DeepCopy(fragmentFreedom);
cloneFragmentFreedom.tag = originalTag;
cloneFragmentFreedom.streamSettings.sockopt ??= new();
cloneFragmentFreedom.streamSettings.sockopt.dialerProxy = afterTag;
outbound.tag = afterTag;
_coreConfig.outbounds.Insert(index, cloneFragmentFreedom);
}
}
private (Mask4Ray tcpFragment, Mask4Ray udpNoise) BuildFragmentsMasks()
{
var configPackets = _config.Fragment4RayItem?.Packets ?? "tlshello";
var configLength = _config.Fragment4RayItem?.Length ?? "50-100";
var configDelay = _config.Fragment4RayItem?.Interval ?? "10-20";
var fragmentMask = new Mask4Ray
{
type = "fragment",
settings = new MaskSettings4Ray
{
packets = configPackets,
length = configLength,
delay = configDelay,
}
};
var noiseMask = new Mask4Ray
{
type = "noise",
settings = new MaskSettings4Ray
{
length = "10-20",
delay = "10-16",
}
};
return (fragmentMask, noiseMask);
}
} }
@@ -25,6 +25,7 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public int? HyUpMbps { get; set; } [Reactive] public int? HyUpMbps { get; set; }
[Reactive] public int? HyDownMbps { get; set; } [Reactive] public int? HyDownMbps { get; set; }
[Reactive] public bool EnableFragment { get; set; } [Reactive] public bool EnableFragment { get; set; }
[Reactive] public bool EnableFinalFragment { get; set; }
#endregion Core #endregion Core
@@ -161,6 +162,7 @@ public class OptionSettingViewModel : MyReactiveObject
HyUpMbps = _config.HysteriaItem.UpMbps; HyUpMbps = _config.HysteriaItem.UpMbps;
HyDownMbps = _config.HysteriaItem.DownMbps; HyDownMbps = _config.HysteriaItem.DownMbps;
EnableFragment = _config.CoreBasicItem.EnableFragment; EnableFragment = _config.CoreBasicItem.EnableFragment;
EnableFinalFragment = _config.CoreBasicItem.EnableFinalFragment;
#endregion Core #endregion Core
@@ -351,6 +353,7 @@ public class OptionSettingViewModel : MyReactiveObject
_config.HysteriaItem.UpMbps = HyUpMbps ?? 0; _config.HysteriaItem.UpMbps = HyUpMbps ?? 0;
_config.HysteriaItem.DownMbps = HyDownMbps ?? 0; _config.HysteriaItem.DownMbps = HyDownMbps ?? 0;
_config.CoreBasicItem.EnableFragment = EnableFragment; _config.CoreBasicItem.EnableFragment = EnableFragment;
_config.CoreBasicItem.EnableFinalFragment = EnableFinalFragment;
_config.GuiItem.AutoRun = AutoRun; _config.GuiItem.AutoRun = AutoRun;
_config.GuiItem.EnableStatistics = EnableStatistics; _config.GuiItem.EnableStatistics = EnableStatistics;
@@ -38,7 +38,7 @@
<Grid <Grid
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
ColumnDefinitions="Auto,Auto,*" 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"> RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock <TextBlock
Grid.Row="0" Grid.Row="0"
@@ -305,19 +305,19 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsBindInterface}" /> Text="{x:Static resx:ResUI.TbEnableFinalFragment}" />
<TextBox <ToggleSwitch
x:Name="txtbindInterface" x:Name="togenableFinalFragment"
Grid.Row="21" Grid.Row="21"
Grid.Column="1" Grid.Column="1"
Width="200" Margin="{StaticResource Margin4}"
Margin="{StaticResource Margin4}" /> HorizontalAlignment="Left" />
<TextBlock <TextBlock
Grid.Row="21" Grid.Row="21"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsBindInterfaceTip}" Text="{x:Static resx:ResUI.TbEnableFinalFragmentTip}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
@@ -325,16 +325,36 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsBindInterface}" />
<TextBox
x:Name="txtbindInterface"
Grid.Row="22"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="22"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsBindInterfaceTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="23"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsSendThrough}" /> Text="{x:Static resx:ResUI.TbSettingsSendThrough}" />
<TextBox <TextBox
x:Name="txtsendThrough" x:Name="txtsendThrough"
Grid.Row="22" Grid.Row="23"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Watermark="0.0.0.0" /> Watermark="0.0.0.0" />
<TextBlock <TextBlock
Grid.Row="22" Grid.Row="23"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -82,6 +82,7 @@ public partial class OptionSettingWindow : WindowBase<OptionSettingViewModel>
this.Bind(ViewModel, vm => vm.HyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.HyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFinalFragment, v => v.togenableFinalFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.AutoRun, v => v.togAutoRun.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.EnableStatistics, v => v.togEnableStatistics.IsChecked).DisposeWith(disposables);
+31 -8
View File
@@ -66,6 +66,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -371,21 +372,20 @@
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsBindInterface}" /> Text="{x:Static resx:ResUI.TbEnableFinalFragment}" />
<TextBox <ToggleButton
x:Name="txtbindInterface" x:Name="togenableFinalFragment"
Grid.Row="21" Grid.Row="21"
Grid.Column="1" Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
Style="{StaticResource DefTextBox}" /> HorizontalAlignment="Left" />
<TextBlock <TextBlock
Grid.Row="21" Grid.Row="21"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsBindInterfaceTip}" Text="{x:Static resx:ResUI.TbEnableFinalFragmentTip}"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
@@ -394,17 +394,40 @@
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsBindInterface}" />
<TextBox
x:Name="txtbindInterface"
Grid.Row="22"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="22"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsBindInterfaceTip}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="23"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbSettingsSendThrough}" /> Text="{x:Static resx:ResUI.TbSettingsSendThrough}" />
<TextBox <TextBox
x:Name="txtsendThrough" x:Name="txtsendThrough"
Grid.Row="22" Grid.Row="23"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
materialDesign:HintAssist.Hint="0.0.0.0" materialDesign:HintAssist.Hint="0.0.0.0"
Style="{StaticResource DefTextBox}" /> Style="{StaticResource DefTextBox}" />
<TextBlock <TextBlock
Grid.Row="22" Grid.Row="23"
Grid.Column="2" Grid.Column="2"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -79,6 +79,7 @@ public partial class OptionSettingWindow
this.Bind(ViewModel, vm => vm.HyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.HyUpMbps, v => v.txtUpMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.HyDownMbps, v => v.txtDownMbps.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.EnableFragment, v => v.togenableFragment.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.EnableFinalFragment, v => v.togenableFinalFragment.IsChecked).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcpmtu, v => v.txtKcpmtu.Text).DisposeWith(disposables); //this.Bind(ViewModel, vm => vm.Kcpmtu, v => v.txtKcpmtu.Text).DisposeWith(disposables);
//this.Bind(ViewModel, vm => vm.Kcptti, v => v.txtKcptti.Text).DisposeWith(disposables); //this.Bind(ViewModel, vm => vm.Kcptti, v => v.txtKcptti.Text).DisposeWith(disposables);