Add UoT support

This commit is contained in:
DHR60 2026-02-18 22:28:43 +08:00
parent b5800f7dfc
commit ce941d8fdf
20 changed files with 93 additions and 12 deletions

View file

@ -30,7 +30,10 @@ public class TuicFmt : BaseFmt
var query = Utils.ParseQueryString(url.Query); var query = Utils.ParseQueryString(url.Query);
ResolveUriQuery(query, ref item); ResolveUriQuery(query, ref item);
item.HeaderType = GetQueryValue(query, "congestion_control"); item.SetProtocolExtra(item.GetProtocolExtra() with
{
CongestionControl = GetQueryValue(query, "congestion_control")
});
return item; return item;
} }
@ -51,7 +54,10 @@ public class TuicFmt : BaseFmt
var dicQuery = new Dictionary<string, string>(); var dicQuery = new Dictionary<string, string>();
ToUriQueryLite(item, ref dicQuery); ToUriQueryLite(item, ref dicQuery);
dicQuery.Add("congestion_control", item.HeaderType); if (!item.GetProtocolExtra().CongestionControl.IsNullOrEmpty())
{
dicQuery.Add("congestion_control", item.GetProtocolExtra().CongestionControl);
}
return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark); return ToUri(EConfigType.TUIC, item.Address, item.Port, $"{item.Username ?? ""}:{item.Password}", dicQuery, remark);
} }

View file

@ -269,12 +269,11 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType); return await SQLiteHelper.Instance.TableAsync<FullConfigTemplateItem>().FirstOrDefaultAsync(it => it.CoreType == eCoreType);
} }
#pragma warning disable CS0618
public async Task MigrateProfileExtra() public async Task MigrateProfileExtra()
{ {
await MigrateProfileExtraGroup(); await MigrateProfileExtraGroup();
#pragma warning disable CS0618
const int pageSize = 100; const int pageSize = 100;
var offset = 0; var offset = 0;
@ -298,7 +297,6 @@ public sealed class AppManager
} }
//await ProfileGroupItemManager.Instance.ClearAll(); //await ProfileGroupItemManager.Instance.ClearAll();
#pragma warning restore CS0618
} }
private async Task<int> MigrateProfileExtraSub(List<ProfileItem> batch) private async Task<int> MigrateProfileExtraSub(List<ProfileItem> batch)
@ -344,6 +342,7 @@ public sealed class AppManager
break; break;
case EConfigType.TUIC: case EConfigType.TUIC:
extra = extra with { CongestionControl = item.HeaderType.NullIfEmpty(), };
item.Username = item.Id; item.Username = item.Id;
item.Id = item.Security; item.Id = item.Security;
item.Password = item.Security; item.Password = item.Security;
@ -400,7 +399,6 @@ public sealed class AppManager
private async Task<bool> MigrateProfileExtraGroup() private async Task<bool> MigrateProfileExtraGroup()
{ {
#pragma warning disable CS0618
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync(); var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
var groupItems = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!)); var groupItems = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!));
@ -465,8 +463,8 @@ public sealed class AppManager
return true; return true;
//await ProfileGroupItemManager.Instance.ClearAll(); //await ProfileGroupItemManager.Instance.ClearAll();
#pragma warning restore CS0618
} }
#pragma warning restore CS0618
#endregion SqliteHelper #endregion SqliteHelper

View file

@ -2,6 +2,9 @@ namespace ServiceLib.Models;
public record ProtocolExtraItem public record ProtocolExtraItem
{ {
public bool? Uot { get; init; }
public string? CongestionControl { get; init; }
// vmess // vmess
public string? AlterId { get; init; } public string? AlterId { get; init; }
public string? VmessSecurity { get; init; } public string? VmessSecurity { get; init; }

View file

@ -133,6 +133,7 @@ public class Outbound4Sbox : BaseServer4Sbox
public int? recv_window_conn { get; set; } public int? recv_window_conn { get; set; }
public int? recv_window { get; set; } public int? recv_window { get; set; }
public bool? disable_mtu_discovery { get; set; } public bool? disable_mtu_discovery { get; set; }
public bool? udp_over_tcp { get; set; }
public string? method { get; set; } public string? method { get; set; }
public string? username { get; set; } public string? username { get; set; }
public string? password { get; set; } public string? password { get; set; }

View file

@ -179,6 +179,8 @@ public class ServersItem4Ray
public string flow { get; set; } public string flow { get; set; }
public bool? uot { get; set; }
public List<SocksUsersItem4Ray> users { get; set; } public List<SocksUsersItem4Ray> users { get; set; }
} }

View file

@ -4464,6 +4464,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 UDP over TCP 的本地化字符串。
/// </summary>
public static string TbUot {
get {
return ResourceManager.GetString("TbUot", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Validate Regional Domain IPs 的本地化字符串。 /// 查找类似 Validate Regional Domain IPs 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value> <value>Configuration item preview</value>
</data> </data>
<data name="TbUot" xml:space="preserve">
<value>UDP over TCP</value>
</data>
</root> </root>

View file

@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value> <value>Configuration item preview</value>
</data> </data>
<data name="TbUot" xml:space="preserve">
<value>UDP over TCP</value>
</data>
</root> </root>

View file

@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value> <value>Configuration item preview</value>
</data> </data>
<data name="TbUot" xml:space="preserve">
<value>UDP over TCP</value>
</data>
</root> </root>

View file

@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value> <value>Configuration item preview</value>
</data> </data>
<data name="TbUot" xml:space="preserve">
<value>UDP over TCP</value>
</data>
</root> </root>

View file

@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value> <value>Configuration item preview</value>
</data> </data>
<data name="TbUot" xml:space="preserve">
<value>UDP over TCP</value>
</data>
</root> </root>

View file

@ -1668,4 +1668,7 @@
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>子配置项预览</value> <value>子配置项预览</value>
</data> </data>
<data name="TbUot" xml:space="preserve">
<value>UDP over TCP</value>
</data>
</root> </root>

View file

@ -1668,4 +1668,7 @@
<data name="menuServerListPreview" xml:space="preserve"> <data name="menuServerListPreview" xml:space="preserve">
<value>Configuration item preview</value> <value>Configuration item preview</value>
</data> </data>
<data name="TbUot" xml:space="preserve">
<value>UDP over TCP</value>
</data>
</root> </root>

View file

@ -35,6 +35,7 @@ public partial class CoreConfigSingboxService
outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod)
? protocolExtra.SsMethod : Global.None; ? protocolExtra.SsMethod : Global.None;
outbound.password = node.Password; outbound.password = node.Password;
outbound.udp_over_tcp = protocolExtra.Uot == true ? true : null;
if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp) if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp)
{ {
@ -192,7 +193,7 @@ public partial class CoreConfigSingboxService
{ {
outbound.uuid = node.Username; outbound.uuid = node.Username;
outbound.password = node.Password; outbound.password = node.Password;
outbound.congestion_control = node.HeaderType; outbound.congestion_control = protocolExtra.CongestionControl;
break; break;
} }
case EConfigType.Anytls: case EConfigType.Anytls:

View file

@ -70,6 +70,7 @@ public partial class CoreConfigV2rayService
serversItem.password = node.Password; serversItem.password = node.Password;
serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod)
? protocolExtra.SsMethod : "none"; ? protocolExtra.SsMethod : "none";
serversItem.uot = protocolExtra.Uot == true ? true : null;
serversItem.ota = false; serversItem.ota = false;
serversItem.level = 1; serversItem.level = 1;

View file

@ -61,6 +61,12 @@ public class AddServerViewModel : MyReactiveObject
[Reactive] [Reactive]
public int WgMtu { get; set; } public int WgMtu { get; set; }
[Reactive]
public bool Uot { get; set; }
[Reactive]
public string CongestionControl { get; set; }
public ReactiveCommand<Unit, Unit> FetchCertCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertCmd { get; }
public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; } public ReactiveCommand<Unit, Unit> FetchCertChainCmd { get; }
public ReactiveCommand<Unit, Unit> SaveCmd { get; } public ReactiveCommand<Unit, Unit> SaveCmd { get; }
@ -123,6 +129,8 @@ public class AddServerViewModel : MyReactiveObject
WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty; WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty;
WgReserved = protocolExtra?.WgReserved ?? string.Empty; WgReserved = protocolExtra?.WgReserved ?? string.Empty;
WgMtu = protocolExtra?.WgMtu ?? 1280; WgMtu = protocolExtra?.WgMtu ?? 1280;
Uot = protocolExtra?.Uot ?? false;
CongestionControl = protocolExtra?.CongestionControl ?? string.Empty;
} }
private async Task SaveServerAsync() private async Task SaveServerAsync()
@ -185,6 +193,8 @@ public class AddServerViewModel : MyReactiveObject
WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(), WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(),
WgReserved = WgReserved.NullIfEmpty(), WgReserved = WgReserved.NullIfEmpty(),
WgMtu = WgMtu >= 576 ? WgMtu : null, WgMtu = WgMtu >= 576 ? WgMtu : null,
Uot = Uot ? true : null,
CongestionControl = CongestionControl.NullIfEmpty(),
}); });
if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) if (await ConfigHandler.AddServer(_config, SelectedSource) == 0)

View file

@ -197,6 +197,19 @@
Width="300" Width="300"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbUot}" />
<ToggleSwitch
x:Name="togUotEnabled3"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock <TextBlock
Grid.Row="4" Grid.Row="4"
Grid.Column="0" Grid.Column="0"
@ -407,7 +420,7 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbPorts7Tips}" /> Text="{x:Static resx:ResUI.TbPorts7Tips}" />
<TextBlock <TextBlock
Grid.Row="4" Grid.Row="4"
Grid.Column="0" Grid.Column="0"
@ -420,7 +433,7 @@
Grid.Column="1" Grid.Column="1"
Width="400" Width="400"
Margin="{StaticResource Margin4}" /> Margin="{StaticResource Margin4}" />
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="5"
Grid.Column="0" Grid.Column="0"

View file

@ -120,6 +120,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break; break;
@ -154,7 +155,7 @@ public partial class AddServerWindow : WindowBase<AddServerViewModel>
case EConfigType.TUIC: case EConfigType.TUIC:
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables);
break; break;
case EConfigType.WireGuard: case EConfigType.WireGuard:

View file

@ -275,6 +275,20 @@
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbUot}" />
<ToggleButton
x:Name="togUotEnabled3"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock <TextBlock
Grid.Row="4" Grid.Row="4"
Grid.Column="0" Grid.Column="0"

View file

@ -115,6 +115,7 @@ public partial class AddServerWindow
case EConfigType.Shadowsocks: case EConfigType.Shadowsocks:
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtId3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SsMethod, v => v.cmbSecurity3.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables);
break; break;
@ -149,7 +150,7 @@ public partial class AddServerWindow
case EConfigType.TUIC: case EConfigType.TUIC:
this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Username, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Password, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.HeaderType, v => v.cmbHeaderType8.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.Text).DisposeWith(disposables);
break; break;
case EConfigType.WireGuard: case EConfigType.WireGuard: