From ce941d8fdf6816231c3f9e1909abde759be20f80 Mon Sep 17 00:00:00 2001 From: DHR60 Date: Wed, 18 Feb 2026 22:28:43 +0800 Subject: [PATCH] Add UoT support --- v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs | 10 ++++++++-- v2rayN/ServiceLib/Manager/AppManager.cs | 8 +++----- v2rayN/ServiceLib/Models/ProtocolExtraItem.cs | 3 +++ v2rayN/ServiceLib/Models/SingboxConfig.cs | 1 + v2rayN/ServiceLib/Models/V2rayConfig.cs | 2 ++ v2rayN/ServiceLib/Resx/ResUI.Designer.cs | 9 +++++++++ v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx | 3 +++ v2rayN/ServiceLib/Resx/ResUI.fr.resx | 3 +++ v2rayN/ServiceLib/Resx/ResUI.hu.resx | 3 +++ v2rayN/ServiceLib/Resx/ResUI.resx | 3 +++ v2rayN/ServiceLib/Resx/ResUI.ru.resx | 3 +++ v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx | 3 +++ v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx | 3 +++ .../Singbox/SingboxOutboundService.cs | 3 ++- .../CoreConfig/V2ray/V2rayOutboundService.cs | 1 + .../ServiceLib/ViewModels/AddServerViewModel.cs | 10 ++++++++++ .../v2rayN.Desktop/Views/AddServerWindow.axaml | 17 +++++++++++++++-- .../Views/AddServerWindow.axaml.cs | 3 ++- v2rayN/v2rayN/Views/AddServerWindow.xaml | 14 ++++++++++++++ v2rayN/v2rayN/Views/AddServerWindow.xaml.cs | 3 ++- 20 files changed, 93 insertions(+), 12 deletions(-) diff --git a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs index 56f78ea9..d1f802b0 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/TuicFmt.cs @@ -30,7 +30,10 @@ public class TuicFmt : BaseFmt var query = Utils.ParseQueryString(url.Query); ResolveUriQuery(query, ref item); - item.HeaderType = GetQueryValue(query, "congestion_control"); + item.SetProtocolExtra(item.GetProtocolExtra() with + { + CongestionControl = GetQueryValue(query, "congestion_control") + }); return item; } @@ -51,7 +54,10 @@ public class TuicFmt : BaseFmt var dicQuery = new Dictionary(); 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); } diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 0e471568..3137f998 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -269,12 +269,11 @@ public sealed class AppManager return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.CoreType == eCoreType); } +#pragma warning disable CS0618 public async Task MigrateProfileExtra() { await MigrateProfileExtraGroup(); -#pragma warning disable CS0618 - const int pageSize = 100; var offset = 0; @@ -298,7 +297,6 @@ public sealed class AppManager } //await ProfileGroupItemManager.Instance.ClearAll(); -#pragma warning restore CS0618 } private async Task MigrateProfileExtraSub(List batch) @@ -344,6 +342,7 @@ public sealed class AppManager break; case EConfigType.TUIC: + extra = extra with { CongestionControl = item.HeaderType.NullIfEmpty(), }; item.Username = item.Id; item.Id = item.Security; item.Password = item.Security; @@ -400,7 +399,6 @@ public sealed class AppManager private async Task MigrateProfileExtraGroup() { -#pragma warning disable CS0618 var list = await SQLiteHelper.Instance.TableAsync().ToListAsync(); var groupItems = new ConcurrentDictionary(list.Where(t => !string.IsNullOrEmpty(t.IndexId)).ToDictionary(t => t.IndexId!)); @@ -465,8 +463,8 @@ public sealed class AppManager return true; //await ProfileGroupItemManager.Instance.ClearAll(); -#pragma warning restore CS0618 } +#pragma warning restore CS0618 #endregion SqliteHelper diff --git a/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs index a768ba80..a76bad45 100644 --- a/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs +++ b/v2rayN/ServiceLib/Models/ProtocolExtraItem.cs @@ -2,6 +2,9 @@ namespace ServiceLib.Models; public record ProtocolExtraItem { + public bool? Uot { get; init; } + public string? CongestionControl { get; init; } + // vmess public string? AlterId { get; init; } public string? VmessSecurity { get; init; } diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index c0cd2e08..2a378f3d 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -133,6 +133,7 @@ public class Outbound4Sbox : BaseServer4Sbox public int? recv_window_conn { get; set; } public int? recv_window { get; set; } public bool? disable_mtu_discovery { get; set; } + public bool? udp_over_tcp { get; set; } public string? method { get; set; } public string? username { get; set; } public string? password { get; set; } diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index 34456a8e..4714357f 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -179,6 +179,8 @@ public class ServersItem4Ray public string flow { get; set; } + public bool? uot { get; set; } + public List users { get; set; } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 6f223693..89488ec9 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -4464,6 +4464,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 UDP over TCP 的本地化字符串。 + /// + public static string TbUot { + get { + return ResourceManager.GetString("TbUot", resourceCulture); + } + } + /// /// 查找类似 Validate Regional Domain IPs 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index dbce6ac4..d3f5e17b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + UDP over TCP + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 005a51d5..dc8a06a1 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1668,4 +1668,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + UDP over TCP + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 3d169590..0e9f88e3 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + UDP over TCP + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 041a103b..cbec15a9 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + UDP over TCP + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 66ebef79..88544787 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1671,4 +1671,7 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + UDP over TCP + \ 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 c8936668..57e861df 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1668,4 +1668,7 @@ 子配置项预览 + + UDP over TCP + \ 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 2ee4dc4f..2bd8b3f3 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1668,4 +1668,7 @@ Configuration item preview + + UDP over TCP + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 178350b3..531092b5 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -35,6 +35,7 @@ public partial class CoreConfigSingboxService outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : Global.None; outbound.password = node.Password; + outbound.udp_over_tcp = protocolExtra.Uot == true ? true : null; if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp) { @@ -192,7 +193,7 @@ public partial class CoreConfigSingboxService { outbound.uuid = node.Username; outbound.password = node.Password; - outbound.congestion_control = node.HeaderType; + outbound.congestion_control = protocolExtra.CongestionControl; break; } case EConfigType.Anytls: diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 7f9c26ca..01ee7ae8 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -70,6 +70,7 @@ public partial class CoreConfigV2rayService serversItem.password = node.Password; serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : "none"; + serversItem.uot = protocolExtra.Uot == true ? true : null; serversItem.ota = false; serversItem.level = 1; diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index ba698c20..73b80c21 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -61,6 +61,12 @@ public class AddServerViewModel : MyReactiveObject [Reactive] public int WgMtu { get; set; } + [Reactive] + public bool Uot { get; set; } + + [Reactive] + public string CongestionControl { get; set; } + public ReactiveCommand FetchCertCmd { get; } public ReactiveCommand FetchCertChainCmd { get; } public ReactiveCommand SaveCmd { get; } @@ -123,6 +129,8 @@ public class AddServerViewModel : MyReactiveObject WgInterfaceAddress = protocolExtra?.WgInterfaceAddress ?? string.Empty; WgReserved = protocolExtra?.WgReserved ?? string.Empty; WgMtu = protocolExtra?.WgMtu ?? 1280; + Uot = protocolExtra?.Uot ?? false; + CongestionControl = protocolExtra?.CongestionControl ?? string.Empty; } private async Task SaveServerAsync() @@ -185,6 +193,8 @@ public class AddServerViewModel : MyReactiveObject WgInterfaceAddress = WgInterfaceAddress.NullIfEmpty(), WgReserved = WgReserved.NullIfEmpty(), WgMtu = WgMtu >= 576 ? WgMtu : null, + Uot = Uot ? true : null, + CongestionControl = CongestionControl.NullIfEmpty(), }); if (await ConfigHandler.AddServer(_config, SelectedSource) == 0) diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 356698fb..5068856d 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -197,6 +197,19 @@ Width="300" Margin="{StaticResource Margin4}" /> + + + - + - + case EConfigType.Shadowsocks: 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.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); break; @@ -154,7 +155,7 @@ public partial class AddServerWindow : WindowBase case EConfigType.TUIC: 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.HeaderType, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.SelectedValue).DisposeWith(disposables); break; case EConfigType.WireGuard: diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index 8c6b4a35..9f3ff5a1 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -275,6 +275,20 @@ Margin="{StaticResource Margin4}" Style="{StaticResource DefComboBox}" /> + + + 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.Uot, v => v.togUotEnabled3.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.MuxEnabled, v => v.togmuxEnabled3.IsChecked).DisposeWith(disposables); break; @@ -149,7 +150,7 @@ public partial class AddServerWindow case EConfigType.TUIC: 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.HeaderType, v => v.cmbHeaderType8.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.CongestionControl, v => v.cmbHeaderType8.Text).DisposeWith(disposables); break; case EConfigType.WireGuard: