Added Tuic v5 support (using sing-box)

pull/4711/head
2dust 2023-12-19 16:03:30 +08:00
parent e2657e746d
commit bd9a4ca094
20 changed files with 629 additions and 384 deletions

View File

@ -77,6 +77,8 @@
public const string hysteria2Protocol = "hysteria2://";
public const string hysteria2Protocol2 = "hy2://";
public const string hysteria2ProtocolLite = "hysteria2";
public const string tuicProtocol = "tuic://";
public const string tuicProtocolLite = "tuic";
public const string userEMail = "t@t.tt";
public const string MyRegPath = "Software\\v2rayNGUI";
@ -165,6 +167,7 @@
public static readonly List<string> TunStacks = new() { "gvisor", "system" };
public static readonly List<string> PresetMsgFilters = new() { "proxy", "direct", "block", "" };
public static readonly List<string> SingboxMuxs = new() { "h2mux", "smux", "yamux", "" };
public static readonly List<string> TuicCongestionControl = new() { "cubic", "new_reno", "bbr" };
#endregion const

View File

@ -729,6 +729,44 @@ namespace v2rayN.Handler
return 0;
}
/// <summary>
/// Add or edit server
/// </summary>
/// <param name="config"></param>
/// <param name="profileItem"></param>
/// <returns></returns>
public static int AddTuicServer(ref Config config, ProfileItem profileItem, bool toFile = true)
{
profileItem.configType = EConfigType.Tuic;
profileItem.coreType = ECoreType.sing_box;
profileItem.address = profileItem.address.TrimEx();
profileItem.id = profileItem.id.TrimEx();
profileItem.security = profileItem.security.TrimEx();
if (!Global.TuicCongestionControl.Contains(profileItem.headerType))
{
profileItem.headerType = Global.TuicCongestionControl.FirstOrDefault()!;
}
if (Utils.IsNullOrEmpty(profileItem.streamSecurity))
{
profileItem.streamSecurity = Global.StreamSecurity;
}
if (Utils.IsNullOrEmpty(profileItem.alpn))
{
profileItem.alpn = "h3";
}
if (profileItem.id.IsNullOrEmpty())
{
return -1;
}
AddServerCommon(ref config, profileItem, toFile);
return 0;
}
public static int SortServers(ref Config config, string subId, string colName, bool asc)
{
var lstModel = LazyConfig.Instance.ProfileItems(subId, "");
@ -1082,6 +1120,10 @@ namespace v2rayN.Handler
{
addStatus = AddHysteria2Server(ref config, profileItem, false);
}
else if (profileItem.configType == EConfigType.Tuic)
{
addStatus = AddTuicServer(ref config, profileItem, false);
}
if (addStatus == 0)
{

View File

@ -294,6 +294,16 @@ namespace v2rayN.Handler
outboundMux(node, outbound);
}
else if (node.configType == EConfigType.Tuic)
{
outbound.type = Global.tuicProtocolLite;
outbound.uuid = node.id;
outbound.password = node.security;
outbound.congestion_control = node.headerType;
outboundMux(node, outbound);
}
outboundTls(node, outbound);

View File

@ -30,6 +30,7 @@ namespace v2rayN.Handler
EConfigType.Trojan => ShareTrojan(item),
EConfigType.VLESS => ShareVLESS(item),
EConfigType.Hysteria2 => ShareHysteria2(item),
EConfigType.Tuic => ShareTuic(item),
_ => null,
};
@ -192,6 +193,35 @@ namespace v2rayN.Handler
return url;
}
private static string ShareTuic(ProfileItem item)
{
string url = string.Empty;
string remark = string.Empty;
if (!Utils.IsNullOrEmpty(item.remarks))
{
remark = "#" + Utils.UrlEncode(item.remarks);
}
var dicQuery = new Dictionary<string, string>();
if (!Utils.IsNullOrEmpty(item.sni))
{
dicQuery.Add("sni", item.sni);
}
if (!Utils.IsNullOrEmpty(item.alpn))
{
dicQuery.Add("alpn", Utils.UrlEncode(item.alpn));
}
dicQuery.Add("congestion_control", item.headerType);
string query = "?" + string.Join("&", dicQuery.Select(x => x.Key + "=" + x.Value).ToArray());
url = string.Format("{0}@{1}:{2}",
$"{item.id}:{item.security}",
GetIpv6(item.address),
item.port);
url = $"{Global.tuicProtocol}{url}{query}{remark}";
return url;
}
private static string GetIpv6(string address)
{
return Utils.IsIpv6(address) ? $"[{address}]" : address;
@ -389,6 +419,10 @@ namespace v2rayN.Handler
profileItem = ResolveHysteria2(result);
}
else if (result.StartsWith(Global.tuicProtocol))
{
profileItem = ResolveTuic(result);
}
else
{
msg = ResUI.NonvmessOrssProtocol;
@ -815,6 +849,32 @@ namespace v2rayN.Handler
return item;
}
private static ProfileItem ResolveTuic(string result)
{
ProfileItem item = new()
{
configType = EConfigType.Tuic
};
Uri url = new(result);
item.address = url.IdnHost;
item.port = url.Port;
item.remarks = url.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
var userInfoParts = url.UserInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length == 2)
{
item.id = userInfoParts[0];
item.security = userInfoParts[1];
}
var query = HttpUtility.ParseQueryString(url.Query);
ResolveStdTransport(query, ref item);
item.headerType = query["congestion_control"] ?? "";
return item;
}
private static int ResolveStdTransport(NameValueCollection query, ref ProfileItem item)
{
item.flow = query["flow"] ?? "";

View File

@ -30,7 +30,7 @@ namespace v2rayN.Handler
_selecteds = new List<ServerTestItem>();
foreach (var it in selecteds)
{
if (it.configType == EConfigType.Custom || it.configType == EConfigType.Hysteria2)
if (it.configType == EConfigType.Custom || it.configType == EConfigType.Hysteria2 || it.configType == EConfigType.Tuic)
{
continue;
}

View File

@ -8,6 +8,7 @@
Socks = 4,
VLESS = 5,
Trojan = 6,
Hysteria2 = 7
Hysteria2 = 7,
Tuic = 8
}
}

View File

@ -54,6 +54,7 @@ namespace v2rayN.Mode
case EConfigType.VLESS:
case EConfigType.Trojan:
case EConfigType.Hysteria2:
case EConfigType.Tuic:
summary += string.Format("{0}({1}:{2})", remarks, addr, port);
break;

View File

@ -109,6 +109,7 @@
public string method { get; set; }
public string username { get; set; }
public string password { get; set; }
public string congestion_control { get; set; }
public string? version { get; set; }
public string? network { get; set; }
public string packet_encoding { get; set; }

File diff suppressed because it is too large Load Diff

View File

@ -148,7 +148,7 @@
<value>Failed to generate default configuration file</value>
</data>
<data name="FailedGetDefaultConfiguration" xml:space="preserve">
<value> Failed to get the default configuration</value>
<value>Failed to get the default configuration</value>
</data>
<data name="FailedImportedCustomServer" xml:space="preserve">
<value>Failed to import custom configuration server</value>
@ -175,13 +175,13 @@
<value>Please fill in the user ID</value>
</data>
<data name="IncorrectClientConfiguration" xml:space="preserve">
<value> is not the correct client configuration file, please check</value>
<value>Is not the correct client configuration file, please check</value>
</data>
<data name="Incorrectconfiguration" xml:space="preserve">
<value> is not the correct configuration, please check</value>
<value>Is not the correct configuration, please check</value>
</data>
<data name="IncorrectServerConfiguration" xml:space="preserve">
<value> is not the correct server configuration file, please check</value>
<value>Is not the correct server configuration file, please check</value>
</data>
<data name="InitialConfiguration" xml:space="preserve">
<value>Initial Configuration</value>
@ -250,7 +250,7 @@
<value>Invalid subscription content</value>
</data>
<data name="MsgUnpacking" xml:space="preserve">
<value>is unpacking...</value>
<value>Is unpacking......</value>
</data>
<data name="MsgUpdateSubscriptionEnd" xml:space="preserve">
<value>Update subscription end</value>
@ -268,7 +268,7 @@
<value>Non-VMess or ss protocol</value>
</data>
<data name="NonVmessService" xml:space="preserve">
<value> non-standard service, this feature is invalid</value>
<value>Non-standard service, this feature is invalid</value>
</data>
<data name="NotFoundCore" xml:space="preserve">
<value>The Core file (file name: {1}) was not found under the folder ({0}), please download and put it in the folder, download address: {2}</value>
@ -277,7 +277,7 @@
<value>Scan completed, no valid QR code found</value>
</data>
<data name="OperationFailed" xml:space="preserve">
<value> operation failed, please check and retry</value>
<value>Operation failed, please check and retry</value>
</data>
<data name="PleaseFillRemarks" xml:space="preserve">
<value>Please Fill Remarks</value>
@ -308,10 +308,10 @@
{0}</value>
</data>
<data name="SuccessfullyImportedCustomServer" xml:space="preserve">
<value>Custom configuration server imported successfully.</value>
<value>Custom configuration server imported successfully</value>
</data>
<data name="SuccessfullyImportedServerViaClipboard" xml:space="preserve">
<value>{0} servers have been imported from clipboard.</value>
<value>{0} servers have been imported from clipboard</value>
</data>
<data name="SuccessfullyImportedServerViaScan" xml:space="preserve">
<value>Scan import URL successfully</value>
@ -1082,7 +1082,7 @@
<value>Enable hardware acceleration(Require restart)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>Waiting for testing</value>
<value>Waiting for testing......</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>Please turn off when there is an abnormal disconnection</value>
@ -1147,4 +1147,10 @@
<data name="TbSettingsUseSystemHosts" xml:space="preserve">
<value>Use System Hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>Add [Tuic] server</value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>Congestion control</value>
</data>
</root>

View File

@ -1082,7 +1082,7 @@
<value>启用硬件加速(需重启)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>等待测试中...</value>
<value>等待测试中......</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>当有异常断流时请关闭</value>
@ -1144,4 +1144,10 @@
<data name="TbSettingsUseSystemHosts" xml:space="preserve">
<value>使用系统hosts</value>
</data>
<data name="menuAddTuicServer" xml:space="preserve">
<value>添加[Tuic]服务器</value>
</data>
<data name="TbHeaderType8" xml:space="preserve">
<value>拥塞控制算法</value>
</data>
</root>

View File

@ -1082,7 +1082,7 @@
<value>啟用硬體加速(需重啟)</value>
</data>
<data name="SpeedtestingWait" xml:space="preserve">
<value>等待測試中...</value>
<value>等待測試中......</value>
</data>
<data name="TipDisplayLog" xml:space="preserve">
<value>當有異常斷流時請關閉</value>
@ -1135,4 +1135,7 @@
<data name="TbRoutingRuleIP" xml:space="preserve">
<value>IP 或 IP CIDR</value>
</data>
</root>
<data name="menuAddTuicServer" xml:space="preserve">
<value>新增[Tuic]伺服器</value>
</data>
</root>

View File

@ -906,8 +906,8 @@ namespace v2rayN
public static T DeepCopy<T>(T obj)
{
object retval;
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new();
BinaryFormatter bf = new();
//序列化成流
bf.Serialize(ms, obj);
ms.Seek(0, SeekOrigin.Begin);

View File

@ -149,6 +149,10 @@ namespace v2rayN.ViewModels
case EConfigType.Hysteria2:
ret = ConfigHandler.AddHysteria2Server(ref _config, item);
break;
case EConfigType.Tuic:
ret = ConfigHandler.AddTuicServer(ref _config, item);
break;
}
if (ret == 0)

View File

@ -90,6 +90,7 @@ namespace v2rayN.ViewModels
public ReactiveCommand<Unit, Unit> AddSocksServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTrojanServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddHysteria2ServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddTuicServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddCustomServerCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaClipboardCmd { get; }
public ReactiveCommand<Unit, Unit> AddServerViaScanCmd { get; }
@ -345,6 +346,10 @@ namespace v2rayN.ViewModels
{
EditServer(true, EConfigType.Hysteria2);
});
AddTuicServerCmd = ReactiveCommand.Create(() =>
{
EditServer(true, EConfigType.Tuic);
});
AddCustomServerCmd = ReactiveCommand.Create(() =>
{
EditServer(true, EConfigType.Custom);

View File

@ -1,12 +1,12 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.AddServerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:resx="clr-namespace:v2rayN.Resx"
xmlns:vms="clr-namespace:v2rayN.ViewModels"
Title="{x:Static resx:ResUI.menuServers}"
@ -450,6 +450,66 @@
Margin="{StaticResource ServerItemMargin}"
Style="{StaticResource DefTextBox}" />
</Grid>
<Grid
x:Name="gridTuic"
Grid.Row="2"
Visibility="Hidden">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource ServerItemMargin}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId}" />
<TextBox
x:Name="txtId8"
Grid.Row="1"
Grid.Column="1"
Width="400"
Margin="{StaticResource ServerItemMargin}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="{StaticResource ServerItemMargin}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbId3}" />
<TextBox
x:Name="txtSecurity8"
Grid.Row="2"
Grid.Column="1"
Width="400"
Margin="{StaticResource ServerItemMargin}"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource ServerItemMargin}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbHeaderType8}" />
<ComboBox
x:Name="cmbHeaderType8"
Grid.Row="3"
Grid.Column="1"
Width="100"
Margin="{StaticResource ServerItemMargin}"
Style="{StaticResource DefComboBox}" />
</Grid>
<Separator
x:Name="sepa2"
@ -785,7 +845,6 @@
Grid.Row="8"
Margin="0,2"
Style="{DynamicResource MaterialDesignSeparator}" />
</Grid>
</ScrollViewer>
</DockPanel>

View File

@ -126,6 +126,20 @@ namespace v2rayN.Views
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
break;
case EConfigType.Tuic:
gridTuic.Visibility = Visibility.Visible;
sepa2.Visibility = Visibility.Collapsed;
gridTransport.Visibility = Visibility.Collapsed;
cmbCoreType.IsEnabled = false;
cmbFingerprint.IsEnabled = false;
cmbFingerprint.Text = string.Empty;
Global.TuicCongestionControl.ForEach(it =>
{
cmbHeaderType8.Items.Add(it);
});
break;
}
gridTlsMore.Visibility = Visibility.Hidden;
@ -169,6 +183,12 @@ namespace v2rayN.Views
case EConfigType.Hysteria2:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId7.Text).DisposeWith(disposables);
break;
case EConfigType.Tuic:
this.Bind(ViewModel, vm => vm.SelectedSource.id, v => v.txtId8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.security, v => v.txtSecurity8.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.headerType, v => v.cmbHeaderType8.Text).DisposeWith(disposables);
break;
}
this.Bind(ViewModel, vm => vm.SelectedSource.network, v => v.cmbNetwork.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SelectedSource.headerType, v => v.cmbHeaderType.Text).DisposeWith(disposables);

View File

@ -37,7 +37,7 @@ namespace v2rayN.Views
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, vm=>vm.useSystemHosts, v=>v.togUseSystemHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.useSystemHosts, v => v.togUseSystemHosts.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.domainStrategy4Freedom, v => v.cmbdomainStrategy4Freedom.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.normalDNS, v => v.txtnormalDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.normalDNS2, v => v.txtnormalDNS2.Text).DisposeWith(disposables);

View File

@ -1,17 +1,17 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
xmlns:base="clr-namespace:v2rayN.Base"
xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:v2rayN.Views"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:v2rayN.Resx"
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
xmlns:vms="clr-namespace:v2rayN.ViewModels"
xmlns:local="clr-namespace:v2rayN.Views"
Title="v2rayN"
Width="900"
Height="700"
@ -87,6 +87,10 @@
x:Name="menuAddHysteria2Server"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddHysteria2Server}" />
<MenuItem
x:Name="menuAddTuicServer"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuAddTuicServer}" />
<Separator Margin="-40,5" />
<MenuItem
x:Name="menuAddServerViaClipboard"

View File

@ -87,6 +87,7 @@ namespace v2rayN.Views
this.BindCommand(ViewModel, vm => vm.AddSocksServerCmd, v => v.menuAddSocksServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTrojanServerCmd, v => v.menuAddTrojanServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddHysteria2ServerCmd, v => v.menuAddHysteria2Server).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddTuicServerCmd, v => v.menuAddTuicServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddCustomServerCmd, v => v.menuAddCustomServer).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaClipboardCmd, v => v.menuAddServerViaClipboard).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.AddServerViaScanCmd, v => v.menuAddServerViaScan).DisposeWith(disposables);