组网的一些更新

This commit is contained in:
snltty
2024-08-08 22:15:26 +08:00
parent 38ef976e13
commit 0f54b9ca17
64 changed files with 739 additions and 488 deletions

View File

@@ -48,18 +48,12 @@ jobs:
env: env:
GITHUB_TOKEN: '${{ secrets.ACTIONS_TOKEN }}' GITHUB_TOKEN: '${{ secrets.ACTIONS_TOKEN }}'
with: with:
tag_name: v1.2.0.3 tag_name: v1.2.0.4
release_name: v1.2.0.3.${{ steps.date.outputs.today }} release_name: v1.2.0.4.${{ steps.date.outputs.today }}
draft: false draft: false
prerelease: false prerelease: false
body: | body: |
1. 要更新 1. 测试中,不要更新
2. 重写的虚拟网卡不再依赖tun2socks(仅windows、linux暂时的)
3. 虚拟网卡支持广播、点对网、网对网了
4. 公开`linker.tun`库可以nuget安装将网卡集成到你的项目中
5. webui显示设备区域设备系统及是否docker
6. 服务器穿透支持端口范围
7. 要更新服务器,要更新服务器,要更新服务器
- name: upload win x64 - name: upload win x64
id: upload-win-x64 id: upload-win-x64

View File

@@ -14,6 +14,16 @@ sidebar_position: 1
6. 打洞失败回退、这需要你中继部署服务器进行中继通信 6. 打洞失败回退、这需要你中继部署服务器进行中继通信
7. 易用的web管理页面 7. 易用的web管理页面
:::danger[win10以下]
1. win7 或 win8 可能需要安装一些环境,才能运行
2. <a href="https://aka.ms/vs/16/release/vc_redist.x64.exe" target="_blank">Microsoft Visual C++ 2015-2019 Redistributable </a>
2. <a href="https://www.microsoft.com/download/details.aspx?id=47442" target="_blank">KB3063858 </a>
:::
:::tip[加入组织] :::tip[加入组织]
<a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">你可以加入QQ群1121552990可以申请一些免费的中继服务器 </a> <a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">你可以加入QQ群1121552990可以申请一些免费的中继服务器 </a>

View File

@@ -8,8 +8,7 @@ sidebar_position: 1
1. 各个设备的`网卡IP`,不要一样,要同一网段,且不应该使用`1``255` 1. 各个设备的`网卡IP`,不要一样,要同一网段,且不应该使用`1``255`
2. 请使用 `10.0.0.0 - 10.255.255.255``172.16.0.0 - 172.31.255.255``192.168.0.0 - 192.168.255.255` 范围内的IP 2. 请使用 `10.0.0.0 - 10.255.255.255``172.16.0.0 - 172.31.255.255``192.168.0.0 - 192.168.255.255` 范围内的IP
3. `局域网IP`,是选填的,可以不填,不懂是啥时,不要填、不要填、不要填 3. 虽然支持UDP广播但是UDP广播不会去主动连接所有设备所以你可以先 ping 以下对方,让两端相互连接
4. 虽然支持UDP广播但是UDP广播不会去主动连接所有设备所以你可以先 ping 以下对方,让两端相互连接
::: :::
@@ -17,20 +16,24 @@ sidebar_position: 1
在设备虚拟网卡一栏点击IP配置 在设备虚拟网卡一栏点击IP配置
![Docusaurus Plushie](./img/tun1.png) ![Docusaurus Plushie](./img/tun-add.png)
![Docusaurus Plushie](./img/tun2.png) ![Docusaurus Plushie](./img/tun-add1.png)
开启网卡成功后,即可通过`虚拟IP`访问目标设备(当然,前提是能够打洞成功,或者中继成功) 开启网卡成功后,即可通过`虚拟IP`访问目标设备(当然,前提是能够打洞成功,或者中继成功)
![Docusaurus Plushie](./img/tun3.png) ![Docusaurus Plushie](./img/tun-open.png)
![Docusaurus Plushie](./img/tun4.png) ![Docusaurus Plushie](./img/tun-open1.png)
试一下能不能连接,第一次连接时,会尝试去打洞或中继,时间可能会比较久,耐心等待 试一下能不能连接,第一次连接时,会尝试去打洞或中继,时间可能会比较久,耐心等待
![Docusaurus Plushie](./img/tun5.png) ![Docusaurus Plushie](./img/tun-ping.png)
## 2、点对网局域网IP ## 2、点对网
:::tip[说明] :::danger[重要]
你要确定你知道你在搞什么如果只是简单的P2P通信上面的内容已经够了以下的东西不要看不要看不要看
:::
:::tip[1、情况1你的设备支持NAT转发时]
1. linux已经自动添加NAT转发 1. linux已经自动添加NAT转发
2. windows你可以尝试在`powershell`运行 `Get-NetNat` 如果没有报错则可以正常使用,如果报错,那你可能需要打开`Hyper-V`才能使用点对网功能, `控制面板\程序\启用或关闭 Windows 功能` 2. windows你可以尝试在`powershell`运行 `Get-NetNat` 如果没有报错则可以正常使用,如果报错,那你可能需要打开`Hyper-V`才能使用点对网功能, `控制面板\程序\启用或关闭 Windows 功能`
@@ -44,25 +47,36 @@ nat on en0 from 192.168.54.0/24 to any -> (en0)
# 加载规则 # 加载规则
sudo pfctl -f /etc/pf.conf -e sudo pfctl -f /etc/pf.conf -e
``` ```
3. 局域网IP是你设备所在局域网的IP不是虚拟网卡IP、不是虚拟网卡IP、不是虚拟网卡IP
::: :::
我这里B端的局域网IP是 `192.168.1.35` 我这里B端的局域网IP是 `192.168.1.35`
![Docusaurus Plushie](./img/tun6.png) ![Docusaurus Plushie](./img/tun-local.png)
![Docusaurus Plushie](./img/tun-local1.png)
尝试连接 对方的局域网IP或者局域网内其它设备(记得关闭防火墙或者防火墙添加一条规则允许ICMP通过) 尝试连接 对方的局域网IP或者局域网内其它设备(记得关闭防火墙或者防火墙添加一条规则允许ICMP通过)
![Docusaurus Plushie](./img/tun7.png) ![Docusaurus Plushie](./img/tun-local-ping.png)
:::tip[2、情况2你的设备无法使用NAT转发时]
1. 你的设备无法使用NAT转发(一般出现在低版本windows下win10以下)那你只能使用windows的端口转发功能来访问你当前设备局域网下的其它设备
2. 按如下配置。当其它设备通过`192.168.54.2:12345` 访问时,将访问到你的局域网的`192.168.1.35:3389`
![Docusaurus Plushie](./img/tun-forward.png)
:::
## 3、网对网 ## 3、网对网
:::tip[说明] :::tip[说明]
1. 如果你把linker安装在路由器上勾选我是网关即可 1. 如果你把linker安装在路由器上可以勾选
2. 假设B端设置的`局域网IP``192.168.1.35/24`那么在A端勾选此项则A端下局域网内的所有设备都能通过`192.168.1.x`去访问B端局域网内的设备
![Docusaurus Plushie](./img/tun8.png) ![Docusaurus Plushie](./img/tun-gateway.png)
::: :::

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View File

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -8,24 +8,26 @@ namespace linker.libs
{ {
public static string Windows(string arg, string[] commands) public static string Windows(string arg, string[] commands)
{ {
return Execute("cmd.exe", arg, commands); return Execute("cmd.exe", arg, commands,out string error);
} }
public static string PowerShell(string arg, string[] commands) public static string PowerShell(string arg, string[] commands, out string error)
{ {
error = string.Empty;
if (IsPowerShellInstalled() == false) if (IsPowerShellInstalled() == false)
{ {
error = "PowerShell is not installed";
return string.Empty; return string.Empty;
} }
return Execute("powershell.exe", arg, commands); return Execute("powershell.exe", arg, commands,out error);
} }
public static string Linux(string arg, string[] commands) public static string Linux(string arg, string[] commands)
{ {
return Execute("/bin/bash", arg, commands); return Execute("/bin/bash", arg, commands, out string error);
} }
public static string Osx(string arg, string[] commands) public static string Osx(string arg, string[] commands)
{ {
return Execute("/bin/bash", arg, commands); return Execute("/bin/bash", arg, commands, out string error);
} }
public static Process Execute(string fileName, string arg) public static Process Execute(string fileName, string arg)
@@ -45,7 +47,7 @@ namespace linker.libs
return proc; return proc;
} }
public static string Execute(string fileName, string arg, string[] commands) public static string Execute(string fileName, string arg, string[] commands,out string error)
{ {
using Process proc = new Process(); using Process proc = new Process();
proc.StartInfo.WorkingDirectory = Path.GetFullPath(Path.Join("./")); proc.StartInfo.WorkingDirectory = Path.GetFullPath(Path.Join("./"));
@@ -68,7 +70,7 @@ namespace linker.libs
proc.StandardInput.AutoFlush = true; proc.StandardInput.AutoFlush = true;
proc.StandardInput.WriteLine("exit"); proc.StandardInput.WriteLine("exit");
proc.StandardInput.Close(); proc.StandardInput.Close();
string error = proc.StandardError.ReadToEnd(); error = proc.StandardError.ReadToEnd();
string output = string.Empty; string output = string.Empty;
if (string.IsNullOrWhiteSpace(error)) if (string.IsNullOrWhiteSpace(error))
{ {

View File

@@ -57,7 +57,7 @@ cmd /c netsh advfirewall firewall add rule name=""{fileName}"" dir=in action=all
string firewall = Path.Join(distPath, "firewall.bat"); string firewall = Path.Join(distPath, "firewall.bat");
File.WriteAllText(firewall, content); File.WriteAllText(firewall, content);
CommandHelper.Execute(firewall, string.Empty, new string[0]); CommandHelper.Execute(firewall, string.Empty, new string[0],out string error);
File.Delete(firewall); File.Delete(firewall);
} }
catch (Exception) catch (Exception)

View File

@@ -61,20 +61,24 @@ namespace linker.libs
static List<string> starts = new() { "10.", "100.", "192.168.", "172." }; static List<string> starts = new() { "10.", "100.", "192.168.", "172." };
static string domain = "linker.snltty.com"; public static ushort GetRouteLevel(string server, out List<IPAddress> result)
public static ushort GetRouteLevel(out List<IPAddress> result)
{ {
if (string.IsNullOrWhiteSpace(server) == false)
{
server = server.Split(':')[0];
}
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
return GetRouteLevelWindows(out result); return GetRouteLevelWindows(server, out result);
} }
return GetRouteLevelLinux(out result); return GetRouteLevelLinux(server, out result);
} }
private static ushort GetRouteLevelLinux(out List<IPAddress> result) private static ushort GetRouteLevelLinux(string server, out List<IPAddress> result)
{ {
result = new List<IPAddress>(); result = new List<IPAddress>();
string str = CommandHelper.Linux(string.Empty, new string[] { $"traceroute {domain} -4 -m 5" }); string str = CommandHelper.Linux(string.Empty, new string[] { $"traceroute {server} -4 -m 5" });
string[] lines = str.Split(Environment.NewLine); string[] lines = str.Split(Environment.NewLine);
Regex regex = new Regex(@"(\d+\.\d+\.\d+\.\d+)"); Regex regex = new Regex(@"(\d+\.\d+\.\d+\.\d+)");
@@ -93,12 +97,12 @@ namespace linker.libs
return 3; return 3;
} }
private static ushort GetRouteLevelWindows(out List<IPAddress> result) private static ushort GetRouteLevelWindows(string server, out List<IPAddress> result)
{ {
result = new List<IPAddress>(); result = new List<IPAddress>();
try try
{ {
IPAddress target = Dns.GetHostEntry(domain).AddressList.FirstOrDefault(c => c.AddressFamily == AddressFamily.InterNetwork); IPAddress target = Dns.GetHostEntry(server).AddressList.FirstOrDefault(c => c.AddressFamily == AddressFamily.InterNetwork);
for (ushort i = 1; i <= 5; i++) for (ushort i = 1; i <= 5; i++)
{ {

View File

@@ -15,8 +15,8 @@
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<Version>1.2.0</Version> <Version>1.2.0</Version>
<AssemblyVersion>1.2.0.3</AssemblyVersion> <AssemblyVersion>1.2.0.4</AssemblyVersion>
<FileVersion>1.2.0.3</FileVersion> <FileVersion>1.2.0.4</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType> <DebugType>full</DebugType>

View File

@@ -1,139 +0,0 @@
using System.Diagnostics;
using System.ServiceProcess;
namespace linker.service
{
partial class LinkerService : ServiceBase
{
private readonly string[] args;
public LinkerService(string[] args)
{
this.args = args;
InitializeComponent();
}
private string mainExeName = "linker";
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
protected override void OnStart(string[] _args)
{
OpenExe();
CheckMainProcess();
}
protected override void OnStop()
{
cancellationTokenSource?.Cancel();
KillExe();
}
private Process proc;
private void CheckMainProcess()
{
Task.Run(async () =>
{
while (cancellationTokenSource.IsCancellationRequested == false)
{
try
{
if (Process.GetProcessesByName(mainExeName).Any() == false)
{
string filename = Process.GetCurrentProcess().MainModule.FileName;
string dir = Path.GetDirectoryName(filename);
if (File.Exists(Path.Combine(dir, $"{mainExeName}.exe.temp")))
{
RestartService();
}
else
{
KillExe();
OpenExe();
}
}
}
catch (Exception)
{
}
await Task.Delay(3000).ConfigureAwait(false);
}
});
}
private bool OpenExe()
{
try
{
string filename = Process.GetCurrentProcess().MainModule.FileName;
string dir = Path.GetDirectoryName(filename);
proc = Process.Start(new ProcessStartInfo()
{
WorkingDirectory = dir,
FileName = Path.Combine(dir, $"{mainExeName}.exe"),
CreateNoWindow = false,
ErrorDialog = false,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden,
//Arguments = string.Join(" ", this.args),
Verb = "runas",
});
return true;
}
catch (Exception)
{
try
{
proc.Kill();
proc.Dispose();
}
catch (Exception)
{
}
proc = null;
}
return false;
}
private void KillExe()
{
try
{
proc?.Close();
proc?.Dispose();
foreach (var item in Process.GetProcessesByName(mainExeName))
{
try
{
item.Kill();
}
catch (Exception)
{
}
}
foreach (var item in Process.GetProcessesByName("tun2socks"))
{
item.Kill();
}
}
catch (Exception)
{
}
finally
{
proc = null;
}
}
public void RestartService()
{
try
{
KillExe();
Environment.Exit(1);
}
catch (Exception)
{
}
}
}
}

View File

@@ -1,24 +0,0 @@
using System.ServiceProcess;
namespace linker.service
{
internal class Program
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += (a, b) =>
{
};
if (OperatingSystem.IsWindows())
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new LinkerService(args)
};
ServiceBase.Run(ServicesToRun);
}
}
}
}

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 清单选项
如果想要更改 Windows 用户帐户控制级别,请使用
以下节点之一替换 requestedExecutionLevel 节点。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
元素。
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
Windows 版本的列表。取消评论适当的元素,
Windows 将自动选择最兼容的环境。 -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI无需
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,43 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<Configurations>Debug;Release</Configurations>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<PublishAot>false</PublishAot>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<Version>1.2.0</Version>
<Authors>snltty</Authors>
<Company>snltty</Company>
<Description>snltty</Description>
<Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>snltty service</PackageReleaseNotes>
<AssemblyVersion>1.2.0.3</AssemblyVersion>
<FileVersion>1.2.0.3</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>D:\desktop\cmonitor\cmonitor.sas.service\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
<ItemGroup>
<Compile Update="LinkerService.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@@ -9,13 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.libs", "linker.libs\
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tests", "linker.tests\linker.tests.csproj", "{04AA3054-5350-4D8B-97F6-31495AE0609D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tests", "linker.tests\linker.tests.csproj", "{04AA3054-5350-4D8B-97F6-31495AE0609D}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.service", "linker.service\linker.service.csproj", "{E8AB5039-3A42-424F-AAEC-A102C8CAA305}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tunnel", "linker.tunnel\linker.tunnel.csproj", "{AFADE8D6-AB00-456B-9F43-53BC95B7B608}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tunnel", "linker.tunnel\linker.tunnel.csproj", "{AFADE8D6-AB00-456B-9F43-53BC95B7B608}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tun", "linker.tun\linker.tun.csproj", "{0DE134E0-7CD8-4DCF-8D2A-325CEBE5895F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tun", "linker.tun\linker.tun.csproj", "{0DE134E0-7CD8-4DCF-8D2A-325CEBE5895F}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "linker.tun.test", "linker.tun.test\linker.tun.test.csproj", "{4A660D3B-76DE-4E6F-9E90-90BA0DBE906A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tun.test", "linker.tun.test\linker.tun.test.csproj", "{4A660D3B-76DE-4E6F-9E90-90BA0DBE906A}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -63,18 +61,6 @@ Global
{04AA3054-5350-4D8B-97F6-31495AE0609D}.Release|x64.Build.0 = Release|Any CPU {04AA3054-5350-4D8B-97F6-31495AE0609D}.Release|x64.Build.0 = Release|Any CPU
{04AA3054-5350-4D8B-97F6-31495AE0609D}.Release|x86.ActiveCfg = Release|Any CPU {04AA3054-5350-4D8B-97F6-31495AE0609D}.Release|x86.ActiveCfg = Release|Any CPU
{04AA3054-5350-4D8B-97F6-31495AE0609D}.Release|x86.Build.0 = Release|Any CPU {04AA3054-5350-4D8B-97F6-31495AE0609D}.Release|x86.Build.0 = Release|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Debug|x64.ActiveCfg = Debug|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Debug|x64.Build.0 = Debug|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Debug|x86.ActiveCfg = Debug|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Debug|x86.Build.0 = Debug|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Release|Any CPU.Build.0 = Release|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Release|x64.ActiveCfg = Release|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Release|x64.Build.0 = Release|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Release|x86.ActiveCfg = Release|Any CPU
{E8AB5039-3A42-424F-AAEC-A102C8CAA305}.Release|x86.Build.0 = Release|Any CPU
{AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Debug|Any CPU.Build.0 = Debug|Any CPU {AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Debug|x64.ActiveCfg = Debug|Any CPU {AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Debug|x64.ActiveCfg = Debug|Any CPU

View File

@@ -1,6 +1,7 @@
using linker.config; using linker.config;
using linker.plugins.serializes; using linker.plugins.serializes;
using linker.plugins.signin.messenger;
using linker.plugins.tuntap.config; using linker.plugins.tuntap.config;
using linker.tunnel.connection; using linker.tunnel.connection;
using linker.tunnel.transport; using linker.tunnel.transport;
@@ -19,30 +20,47 @@ namespace linker.Tests
MemoryPackFormatterProvider.Register(new IPAddressFormatter()); MemoryPackFormatterProvider.Register(new IPAddressFormatter());
TuntapInfo tuntapInfo = new TuntapInfo
byte[] bytes = MemoryPackSerializer.Serialize(new SignInListRequestInfo1
{ {
Error = "dfgdgdfgdfgddfgdfhdhdhdhdfhdfdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", GroupId = string.Empty,
Gateway = false, Ids =[],
IP = IPAddress.Any, Name = "11",
LanIPs = new IPAddress[] { IPAddress.Any, IPAddress.Loopback, IPAddress.Broadcast }, Page = 1,
Masks = [24, 24, 24], Size = 1,
MachineId = "dfgdgdfgdfgddfgdfhdhdhdhdfhdfdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", });
Status = TuntapStatus.Normal,
System = "dfgdgdfgdfgddfgdfhdhdhdhdfhdfdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
};
List<TuntapInfo> tuntapInfos = new List<TuntapInfo>();
for (int i = 0; i < 100; i++)
{
tuntapInfos.Add(tuntapInfo);
}
byte[] bytes = MemoryPackSerializer.Serialize(tuntapInfos); SignInListRequestInfo tuntapInfos1 = MemoryPackSerializer.Deserialize<SignInListRequestInfo>(bytes);
List<TuntapInfo> tuntapInfos1 = MemoryPackSerializer.Deserialize<List<TuntapInfo>>(bytes); Assert.AreEqual(tuntapInfos1.Name, "11");
Assert.AreEqual(tuntapInfos1.Count, tuntapInfos.Count);
} }
} }
[MemoryPackable]
public sealed partial class SignInListRequestInfo1
{
/// <summary>
/// <20><>ǰҳ
/// </summary>
public int Page { get; set; } = 1;
/// <summary>
/// ÿҳ<C3BF><D2B3>С
/// </summary>
public int Size { get; set; } = 10;
/// <summary>
/// <20><><EFBFBD>ڷ<EFBFBD><DAB7><EFBFBD>
/// </summary>
public string GroupId { get; set; }
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public string Name { get; set; }
/// <summary>
/// <20><>id<69><64>ȡ
/// </summary>
public string[] Ids { get; set; }
}
} }

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.59" ProductVersion="0.0.0.59" publishDir="/dist/" dstrip="false" local="false" ignored="false"> <project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.63" ProductVersion="0.0.0.63" publishDir="/dist/" dstrip="false" local="false" ignored="false">
<file name="main.aardio" path="main.aardio" comment="main.aardio"/> <file name="main.aardio" path="main.aardio" comment="main.aardio"/>
<folder name="资源文件" path="res" embed="true" local="false" ignored="false"> <folder name="资源文件" path="res" embed="true" local="false" ignored="false">
<file name="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/> <file name="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/>

Binary file not shown.

View File

@@ -30,7 +30,7 @@ catch(e){
mainForm.serviceName = "linker.service"; mainForm.serviceName = "linker.service";
mainForm.exeName = "linker.service.exe"; mainForm.exeName = "linker.exe";
import win.image; import win.image;
hIcon = win.image.loadIconFromFile("/res/favicon.ico",true); hIcon = win.image.loadIconFromFile("/res/favicon.ico",true);
@@ -51,6 +51,8 @@ mainForm.wndproc = function(hwnd,message,wParam,lParam){
//左键 //左键
if(lParam === 0x0202){ if(lParam === 0x0202){
mainForm.show(); mainForm.show();
win.setTopmost(mainForm.hwnd);
win.setTopmost(mainForm.hwnd,false);
} }
} }
//关闭 //关闭
@@ -248,6 +250,8 @@ mainForm.showPopmenu();
if(!_ARGV["task"]) if(!_ARGV["task"])
{ {
mainForm.show(); mainForm.show();
win.setTopmost(mainForm.hwnd);
win.setTopmost(mainForm.hwnd,false);
} }
mainForm.setInterval( mainForm.setInterval(
@@ -257,12 +261,15 @@ mainForm.setInterval(
try{ try{
if(io.exist(io._exefile+".temp")) if(io.exist(io._exefile+".temp"))
{ {
mainForm.reStart();
/*
var prcs = process.popen.cmd("tasklist | findstr "+mainForm.serviceName); var prcs = process.popen.cmd("tasklist | findstr "+mainForm.serviceName);
var str = prcs.readAll(); var str = prcs.readAll();
if(!!!string.indexOf(str,mainForm.serviceName)) if(!!!string.indexOf(str,mainForm.serviceName))
{ {
mainForm.reStart(); mainForm.reStart();
} }
*/
} }
}catch(e) }catch(e)
{ {

View File

@@ -1,5 +1,6 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.Json.Serialization;
namespace linker.tun namespace linker.tun
{ {
@@ -50,6 +51,18 @@ namespace linker.tun
/// </summary> /// </summary>
public void RemoveNat(out string error); public void RemoveNat(out string error);
/// <summary>
/// 添加端口转发
/// </summary>
/// <param name="forwards"></param>
public void AddForward(List<LinkerTunDeviceForwardItem> forwards);
/// <summary>
/// 删除端口转发
/// </summary>
/// <param name="forwards"></param>
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards);
/// <summary> /// <summary>
/// 添加路由 /// 添加路由
/// </summary> /// </summary>
@@ -90,6 +103,17 @@ namespace linker.tun
public Task Callback(LinkerTunDevicPacket packet); public Task Callback(LinkerTunDevicPacket packet);
} }
public sealed class LinkerTunDeviceForwardItem
{
public IPAddress ListenAddr { get; set; } = IPAddress.Any;
public int ListenPort { get; set; }
public IPAddress ConnectAddr { get; set; } = IPAddress.Any;
public int ConnectPort { get; set; }
[JsonIgnore]
public bool Enable => ListenPort > 0 && ConnectAddr.Equals(IPAddress.Any) == false && ConnectPort > 0;
}
/// <summary> /// <summary>
/// 数据包 /// 数据包
/// </summary> /// </summary>

View File

@@ -162,6 +162,15 @@ namespace linker.tun
} }
} }
public void AddForward(List<LinkerTunDeviceForwardItem> forwards)
{
}
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards)
{
}
public void AddRoute(LinkerTunDeviceRouteItem[] ips, IPAddress ip, bool gateway) public void AddRoute(LinkerTunDeviceRouteItem[] ips, IPAddress ip, bool gateway)
{ {
if (gateway) if (gateway)
@@ -276,8 +285,9 @@ namespace linker.tun
public void Clear() public void Clear()
{ {
} }
} }

View File

@@ -112,6 +112,14 @@ namespace linker.tun
error = string.Empty; error = string.Empty;
} }
public void AddForward(List<LinkerTunDeviceForwardItem> forwards)
{
}
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards)
{
}
private byte[] buffer = new byte[2 * 1024]; private byte[] buffer = new byte[2 * 1024];
public ReadOnlyMemory<byte> Read() public ReadOnlyMemory<byte> Read()
{ {

View File

@@ -16,6 +16,10 @@ namespace linker.tun
private string error = string.Empty; private string error = string.Empty;
public string Error => error; public string Error => error;
private string error1 = string.Empty;
public string Error1 => error1;
private uint operating = 0; private uint operating = 0;
public LinkerTunDeviceStatus Status public LinkerTunDeviceStatus Status
{ {
@@ -125,7 +129,6 @@ namespace linker.tun
{ {
cancellationTokenSource?.Cancel(); cancellationTokenSource?.Cancel();
linkerTunDevice?.Shutdown(); linkerTunDevice?.Shutdown();
linkerTunDevice?.RemoveNat(out error);
} }
catch (Exception) catch (Exception)
{ {
@@ -142,14 +145,31 @@ namespace linker.tun
/// </summary> /// </summary>
public void SetNat() public void SetNat()
{ {
linkerTunDevice?.SetNat(out error); linkerTunDevice?.SetNat(out error1);
} }
/// <summary> /// <summary>
/// 移除NAT转发 /// 移除NAT转发
/// </summary> /// </summary>
public void RemoveNat() public void RemoveNat()
{ {
linkerTunDevice?.RemoveNat(out error); linkerTunDevice?.RemoveNat(out error1);
}
/// <summary>
/// 添加端口转发
/// </summary>
/// <param name="forwards"></param>
public void AddForward(List<LinkerTunDeviceForwardItem> forwards)
{
linkerTunDevice?.AddForward(forwards);
}
/// <summary>
/// 移除端口转发
/// </summary>
/// <param name="forwards"></param>
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards)
{
linkerTunDevice?.RemoveForward(forwards);
} }
/// <summary> /// <summary>

View File

@@ -118,9 +118,9 @@ namespace linker.tun
error = string.Empty; error = string.Empty;
try try
{ {
CommandHelper.PowerShell($"start-service WinNat", []); CommandHelper.PowerShell($"start-service WinNat", [], out error);
IPAddress network = NetworkHelper.ToNetworkIp(this.address, NetworkHelper.MaskValue(prefixLength)); IPAddress network = NetworkHelper.ToNetworkIp(this.address, NetworkHelper.MaskValue(prefixLength));
CommandHelper.PowerShell($"New-NetNat -Name {Name} -InternalIPInterfaceAddressPrefix {network}/{prefixLength}", []); CommandHelper.PowerShell($"New-NetNat -Name {Name} -InternalIPInterfaceAddressPrefix {network}/{prefixLength}", [], out error);
try try
{ {
@@ -148,9 +148,9 @@ namespace linker.tun
try try
{ {
CommandHelper.PowerShell($"start-service WinNat", []); CommandHelper.PowerShell($"start-service WinNat", [], out error);
CommandHelper.PowerShell($"Remove-NetNat -Name {Name} -Confirm:$false", []); CommandHelper.PowerShell($"Remove-NetNat -Name {Name} -Confirm:$false", [], out error);
try try
{ {
var scope = new ManagementScope(@"root\StandardCimv2"); var scope = new ManagementScope(@"root\StandardCimv2");
@@ -176,6 +176,25 @@ namespace linker.tun
} }
} }
public void AddForward(List<LinkerTunDeviceForwardItem> forwards)
{
string[] commands = forwards.Where(c => c != null && c.Enable).Select(c =>
{
return $"netsh interface portproxy add v4tov4 listenaddress={c.ListenAddr} listenport={c.ListenPort} connectaddress={c.ConnectAddr} connectport={c.ConnectPort}";
}).ToArray();
CommandHelper.Windows(string.Empty, commands);
}
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards)
{
string[] commands = forwards.Where(c => c != null && c.Enable).Select(c =>
{
return $"netsh interface portproxy delete v4tov4 listenport={c.ListenPort} listenaddress={c.ListenAddr}";
}).ToArray();
CommandHelper.Windows(string.Empty, commands);
}
public void AddRoute(LinkerTunDeviceRouteItem[] ips, IPAddress ip, bool gateway) public void AddRoute(LinkerTunDeviceRouteItem[] ips, IPAddress ip, bool gateway)
{ {
if (interfaceNumber > 0) if (interfaceNumber > 0)

View File

@@ -17,8 +17,8 @@
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>linker tun</PackageReleaseNotes> <PackageReleaseNotes>linker tun</PackageReleaseNotes>
<AssemblyVersion>1.2.0.3</AssemblyVersion> <AssemblyVersion>1.2.0.4</AssemblyVersion>
<FileVersion>1.2.0.3</FileVersion> <FileVersion>1.2.0.4</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@@ -17,8 +17,8 @@
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>linker tunnel</PackageReleaseNotes> <PackageReleaseNotes>linker tunnel</PackageReleaseNotes>
<AssemblyVersion>1.2.0.3</AssemblyVersion> <AssemblyVersion>1.2.0.4</AssemblyVersion>
<FileVersion>1.2.0.3</FileVersion> <FileVersion>1.2.0.4</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@@ -230,7 +230,7 @@ namespace linker.tunnel.transport
return true; return true;
} }
} }
}).AsTask().WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false); }).AsTask().WaitAsync(TimeSpan.FromMilliseconds(1000)).ConfigureAwait(false);
QuicStream quicStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional).ConfigureAwait(false); QuicStream quicStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional).ConfigureAwait(false);
return new TunnelConnectionMsQuic return new TunnelConnectionMsQuic
{ {

View File

@@ -11,6 +11,9 @@ export const setSignInServers = (servers) => {
export const getSignInfo = () => { export const getSignInfo = () => {
return sendWebsocketMsg('signInclient/info'); return sendWebsocketMsg('signInclient/info');
} }
export const setSignInIndex = (ids) => {
return sendWebsocketMsg('signInclient/setindex', ids);
}
export const getSignInList = (data) => { export const getSignInList = (data) => {
return sendWebsocketMsg('signInclient/List', data); return sendWebsocketMsg('signInclient/List', data);
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<el-table-column prop="MachineId" label="设备"> <el-table-column prop="MachineId" label="设备" width="240">
<template #header> <template #header>
<div class="flex"> <div class="flex">
<span class="flex-1">设备</span> <span class="flex-1">设备</span>
@@ -13,7 +13,7 @@
<div> <div>
<p> <p>
<template v-if="tuntap.list[scope.row.MachineId] && tuntap.list[scope.row.MachineId].system"> <template v-if="tuntap.list[scope.row.MachineId] && tuntap.list[scope.row.MachineId].system">
<span :title="tuntap.list[scope.row.MachineId].System"> <span :title="tuntap.list[scope.row.MachineId].SystemInfo">
<img v-if="scope.row.countryFlag" class="system" :src="scope.row.countryFlag" /> <img v-if="scope.row.countryFlag" class="system" :src="scope.row.countryFlag" />
<img class="system":src="`/${tuntap.list[scope.row.MachineId].system}.svg`" /> <img class="system":src="`/${tuntap.list[scope.row.MachineId].system}.svg`" />
<img v-if="tuntap.list[scope.row.MachineId].systemDocker" class="system" src="/docker.svg" /> <img v-if="tuntap.list[scope.row.MachineId].systemDocker" class="system" src="/docker.svg" />

View File

@@ -1,5 +1,13 @@
<template> <template>
<div class="home-list-wrap absolute" > <div class="home-list-wrap absolute" >
<el-table border style="width: 100%" height="32px" size="small" @sort-change="handleSortChange" class="table-sort">
<el-table-column prop="MachineId" label="设备名" width="120" sortable ></el-table-column>
<el-table-column prop="Version" label="版本" width="120" sortable></el-table-column>
<el-table-column prop="tunnel" label="网关" width="90" sortable></el-table-column>
<el-table-column prop="tuntap" label="网卡IP" width="150" sortable></el-table-column>
<el-table-column prop="forward" label=""></el-table-column>
<el-table-column label="" width="74" fixed="right"></el-table-column>
</el-table>
<el-table :data="devices.page.List" stripe border style="width: 100%" :height="`${state.height}px`" size="small"> <el-table :data="devices.page.List" stripe border style="width: 100%" :height="`${state.height}px`" size="small">
<Device @edit="handleDeviceEdit" @refresh="handlePageRefresh"></Device> <Device @edit="handleDeviceEdit" @refresh="handlePageRefresh"></Device>
<Tunnel @edit="handleTunnelEdit" @refresh="handleTunnelRefresh" @connections="handleTunnelConnections"></Tunnel> <Tunnel @edit="handleTunnelEdit" @refresh="handleTunnelRefresh" @connections="handleTunnelConnections"></Tunnel>
@@ -55,14 +63,14 @@ export default {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const state = reactive({ const state = reactive({
height: computed(()=>globalData.value.height-60), height: computed(()=>globalData.value.height-90),
}); });
const {devices, machineId, _getSignList, _getSignList1, const {devices, machineId, _getSignList, _getSignList1,
handleDeviceEdit, handlePageChange, handlePageSizeChange, handleDel,clearDevicesTimeout} = provideDevices(); handleDeviceEdit, handlePageChange, handlePageSizeChange, handleDel,clearDevicesTimeout,setSort} = provideDevices();
const {tuntap,_getTuntapInfo,handleTuntapEdit,handleTuntapRefresh,clearTuntapTimeout,getTuntapMachines} = provideTuntap(); const {tuntap,_getTuntapInfo,handleTuntapEdit,handleTuntapRefresh,clearTuntapTimeout,getTuntapMachines,sortTuntapIP} = provideTuntap();
const {tunnel,_getTunnelInfo,handleTunnelEdit,handleTunnelRefresh,clearTunnelTimeout} = provideTunnel(); const {tunnel,_getTunnelInfo,handleTunnelEdit,handleTunnelRefresh,clearTunnelTimeout,sortTunnel} = provideTunnel();
const {forward,_getForwardInfo,handleForwardEdit,_testTargetForwardInfo,clearForwardTimeout,getForwardMachines} = provideForward(); const {forward,_getForwardInfo,handleForwardEdit,_testTargetForwardInfo,clearForwardTimeout,getForwardMachines} = provideForward();
const {sforward,_getSForwardInfo,handleSForwardEdit,_testLocalSForwardInfo,clearSForwardTimeout,getSForwardMachines} = provideSforward(); const {sforward,_getSForwardInfo,handleSForwardEdit,_testLocalSForwardInfo,clearSForwardTimeout,getSForwardMachines} = provideSforward();
const {connections, const {connections,
@@ -73,6 +81,30 @@ export default {
const {_getUpdater,clearUpdaterTimeout} = provideUpdater(); const {_getUpdater,clearUpdaterTimeout} = provideUpdater();
const handleSortChange = (row)=>{
devices.page.Request.Prop = row.prop;
devices.page.Request.Asc = row.order == 'ascending';
let fn = new Promise((resolve,reject)=>{
resolve();
});
if(row.prop == 'tunnel'){
const ids = sortTunnel(devices.page.Request.Asc);
if(ids .length > 0){
fn = setSort(ids);
}
}else if(row.prop == 'tuntap'){
const ids = sortTuntapIP(devices.page.Request.Asc);
if(ids .length > 0){
fn = setSort(ids);
}
}
fn.then(()=>{
handlePageChange();
}).catch(()=>{});
}
const _handleForwardEdit = (machineId) => { const _handleForwardEdit = (machineId) => {
handleForwardEdit(machineId,devices.page.List.filter(c => c.MachineId == machineId)[0].MachineName); handleForwardEdit(machineId,devices.page.List.filter(c => c.MachineId == machineId)[0].MachineName);
} }
@@ -142,7 +174,7 @@ export default {
}); });
return { return {
state,devices, machineId, state,devices, machineId,handleSortChange,
handleDeviceEdit,handlePageRefresh,handlePageSearch, handlePageChange,handlePageSizeChange, handleDel, handleDeviceEdit,handlePageRefresh,handlePageSearch, handlePageChange,handlePageSizeChange, handleDel,
tuntap, handleTuntapEdit, handleTuntapRefresh, tuntap, handleTuntapEdit, handleTuntapRefresh,
tunnel,connections, handleTunnelEdit, handleTunnelRefresh,handleTunnelConnections, tunnel,connections, handleTunnelEdit, handleTunnelRefresh,handleTunnelConnections,
@@ -152,7 +184,18 @@ export default {
} }
} }
</script> </script>
<style lang="stylus">
.table-sort.el-table
{
th.el-table__cell.is-leaf{border-bottom:0}
.el-table__inner-wrapper:before{height:0}
}
</style>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.table-sort
{
th{border-bottom:0}
}
.home-list-wrap{ .home-list-wrap{
padding:1rem; padding:1rem;

View File

@@ -10,8 +10,8 @@
</a> </a>
</div> </div>
<div> <div>
<a href="javascript:;" class="a-line" @click="handleConnections(scope.row.MachineId)"> <a href="javascript:;" class="a-line" :class="{green:connectionCount(scope.row.MachineId)>0}" @click="handleConnections(scope.row.MachineId)">
<span>连接数 : {{connectionCount(scope.row.MachineId)}}</span> 连接数 : <span>{{connectionCount(scope.row.MachineId)}}</span>
</a> </a>
</div> </div>
</template> </template>
@@ -57,4 +57,6 @@ export default {
<style lang="stylus" scoped> <style lang="stylus" scoped>
.el-switch.is-disabled{opacity :1;} .el-switch.is-disabled{opacity :1;}
.green{font-weight:bold;}
</style> </style>

View File

@@ -33,9 +33,28 @@
</template> </template>
</div> </div>
<div> <div>
<div v-for="(item,index) in tuntap.list[scope.row.MachineId].LanIPs" :key="index"> <template v-if="tuntap.list[scope.row.MachineId].Error1">
{{ item }} / {{ tuntap.list[scope.row.MachineId].Masks[index] }} <el-popover placement="top" title="msg" width="20rem" trigger="hover" :content="tuntap.list[scope.row.MachineId].Error1">
</div> <template #reference>
<div class="yellow">
<template v-for="(item,index) in tuntap.list[scope.row.MachineId].LanIPs" :key="index">
<div>
{{ item }} / {{ tuntap.list[scope.row.MachineId].Masks[index] }}
</div>
</template>
</div>
</template>
</el-popover>
</template>
<template v-else>
<div>
<template v-for="(item,index) in tuntap.list[scope.row.MachineId].LanIPs" :key="index">
<div>
{{ item }} / {{ tuntap.list[scope.row.MachineId].Masks[index] }}
</div>
</template>
</div>
</template>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,29 +1,55 @@
<template> <template>
<el-dialog v-model="state.show" :close-on-click-modal="false" append-to=".app-wrap" title="设置虚拟网卡IP" width="420"> <el-dialog v-model="state.show" :close-on-click-modal="false" append-to=".app-wrap" title="组网设置" top="1vh" width="700">
<div> <div>
<el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="80"> <el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="140">
<el-form-item prop="gateway" style="margin-bottom:0"> <el-form-item prop="gateway" style="margin-bottom:0">
<el-checkbox v-model="state.ruleForm.Gateway" label="我在路由器,我是网关" size="large" /> 赐予此设备IP其它可以通过这个IP访问
</el-form-item> </el-form-item>
<el-form-item label="虚拟网卡IP" prop="IP"> <el-form-item label="此设备的虚拟网卡IP" prop="IP">
<el-input v-model="state.ruleForm.IP" style="width:12rem" /> / 24 <el-input v-model="state.ruleForm.IP" style="width:12rem" /> / <el-input @change="handlePrefixLengthChange" v-model="state.ruleForm.PrefixLength" style="width:4rem" />
</el-form-item> </el-form-item>
<el-form-item style="margin-bottom:0"> <el-form-item prop="upgrade" style="margin-bottom:0">
<div>局域网IP选填不懂是啥的时候不要填</div> <el-checkbox v-model="state.ruleForm.Upgrade" label="我很懂,我要使用高级功能(点对网和网对网)" size="large" />
</el-form-item> </el-form-item>
<el-form-item label="局域网IP" prop="LanIP"> <div class="upgrade-wrap" v-if="state.ruleForm.Upgrade">
<template v-for="(item, index) in state.ruleForm.LanIPs" :key="index"> <el-form-item prop="gateway" style="margin-bottom:0">
<div class="flex" style="margin-bottom:.6rem"> <el-checkbox v-model="state.ruleForm.Gateway" label="此设备在路由器(网对网,将对方的 局域网IP 转发到对方)" size="large" />
<div class="flex-1"> </el-form-item>
<el-input v-model="state.ruleForm.LanIPs[index]" style="width:12rem" /> / <el-input @change="handleMaskChange(index)" v-model="state.ruleForm.Masks[index]" style="width:4rem" /> <el-form-item prop="gateway" style="margin-bottom:0">
<span class="yellow">此设备可以用NAT转发那填写局域网IP其它交给NAT(linuxmacoswin10+)</span>
</el-form-item>
<el-form-item label="此设备局域网IP" prop="LanIP">
<template v-for="(item, index) in state.ruleForm.LanIPs" :key="index">
<div class="flex" style="margin-bottom:.6rem">
<div class="flex-1">
<el-input v-model="state.ruleForm.LanIPs[index]" style="width:12rem" /> / <el-input @change="handleMaskChange(index)" v-model="state.ruleForm.Masks[index]" style="width:4rem" />
</div>
<div class="pdl-10">
<el-button type="danger" @click="handleDel(index)"><el-icon><Delete /></el-icon></el-button>
<el-button type="primary" @click="handleAdd(index)"><el-icon><Plus /></el-icon></el-button>
</div>
</div> </div>
<div class="pdl-10"> </template>
<el-button type="danger" @click="handleDel(index)"><el-icon><Delete /></el-icon></el-button> </el-form-item>
<el-button type="primary" @click="handleAdd(index)"><el-icon><Plus /></el-icon></el-button> <el-form-item prop="gateway" style="margin-bottom:0">
<span class="yellow">此设备不能用NAT转发那可以使用系统端口转发实现类似的效果(仅windows)</span>
</el-form-item>
<el-form-item label="端口转发" prop="foreards">
<template v-for="(item, index) in state.ruleForm.Forwards" :key="index">
<div class="flex" style="margin-bottom:.6rem">
<div class="flex-1">
<el-input v-model="item.ListenAddr" style="width:7rem" readonly /> : <el-input @change="handleForwardChange(index)" v-model="item.ListenPort" style="width:6rem" />
-> <el-input v-model="item.ConnectAddr" style="width:12rem" /> : <el-input @change="handleForwardChange(index)" v-model="item.ConnectPort" style="width:6rem" />
</div>
<div class="pdl-10">
<el-button type="danger" @click="handleDelForward(index)"><el-icon><Delete /></el-icon></el-button>
<el-button type="primary" @click="handleAddForward(index)"><el-icon><Plus /></el-icon></el-button>
</div>
</div> </div>
</div> </template>
</template> </el-form-item>
</el-form-item> </div>
<el-form-item label="" prop="Btns"> <el-form-item label="" prop="Btns">
<div> <div>
<el-button @click="state.show = false">取消</el-button> <el-button @click="state.show = false">取消</el-button>
@@ -57,7 +83,13 @@ export default {
IP: tuntap.value.current.IP, IP: tuntap.value.current.IP,
LanIPs: tuntap.value.current.LanIPs.slice(0), LanIPs: tuntap.value.current.LanIPs.slice(0),
Masks: tuntap.value.current.Masks.slice(0), Masks: tuntap.value.current.Masks.slice(0),
PrefixLength:tuntap.value.current.PrefixLength || 24,
Gateway: tuntap.value.current.Gateway, Gateway: tuntap.value.current.Gateway,
Upgrade: tuntap.value.current.Upgrade,
Forwards:tuntap.value.current.Forwards.slice(0) || [
{ListenAddr:'0.0.0.0',ListenPort:0,ConnectAddr:'0.0.0.0',ConnectPort:0}
]
}, },
rules: {} rules: {}
}); });
@@ -73,6 +105,13 @@ export default {
} }
}); });
const handlePrefixLengthChange = ()=>{
var value = +state.ruleForm.PrefixLength;
if(value>32 || value<16 || isNaN(value)){
value = 24;
}
state.ruleForm.PrefixLength = value;
}
const handleMaskChange = (index)=>{ const handleMaskChange = (index)=>{
var value = +state.ruleForm.Masks[index]; var value = +state.ruleForm.Masks[index];
if(value>32 || value<16 || isNaN(value)){ if(value>32 || value<16 || isNaN(value)){
@@ -81,9 +120,11 @@ export default {
state.ruleForm.Masks[index] = value; state.ruleForm.Masks[index] = value;
} }
const handleDel = (index) => { const handleDel = (index) => {
if (state.ruleForm.LanIPs.length == 1) return;
state.ruleForm.LanIPs.splice(index, 1); state.ruleForm.LanIPs.splice(index, 1);
state.ruleForm.Masks.splice(index, 1); state.ruleForm.Masks.splice(index, 1);
if (state.ruleForm.LanIPs.length == 0){
handleAdd(0);
}
} }
const handleAdd = (index) => { const handleAdd = (index) => {
state.ruleForm.LanIPs.splice(index + 1, 0, ''); state.ruleForm.LanIPs.splice(index + 1, 0, '');
@@ -101,7 +142,14 @@ export default {
},{lanips:[],masks:[]}); },{lanips:[],masks:[]});
json.LanIPs = lanips; json.LanIPs = lanips;
json.Masks = masks; json.Masks = masks;
json.PrefixLength = +state.ruleForm.PrefixLength;
json.Gateway = state.ruleForm.Gateway; json.Gateway = state.ruleForm.Gateway;
json.Upgrade = state.ruleForm.Upgrade;
json.Forwards = state.ruleForm.Forwards;
json.Forwards.forEach(c=>{
c.ListenPort=+c.ListenPort;
c.ConnectPort=+c.ConnectPort;
});
updateTuntap(json).then(() => { updateTuntap(json).then(() => {
state.show = false; state.show = false;
ElMessage.success('已操作!'); ElMessage.success('已操作!');
@@ -111,12 +159,32 @@ export default {
}); });
} }
const handleDelForward = (index) => {
state.ruleForm.Forwards.splice(index, 1);
if (state.ruleForm.Forwards.length == 0) {
handleAddForward(0);
}
}
const handleAddForward = (index) => {
state.ruleForm.Forwards.splice(index + 1, 0, {ListenAddr:'0.0.0.0',ListenPort:0,ConnectAddr:'0.0.0.0',ConnectPort:0});
}
const handleForwardChange = ()=>{
}
return { return {
state, ruleFormRef,handleMaskChange, handleDel, handleAdd, handleSave state, ruleFormRef,handlePrefixLengthChange,handleMaskChange, handleDel, handleAdd, handleSave,
handleForwardChange,handleDelForward,handleAddForward
} }
} }
} }
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.el-switch.is-disabled{opacity :1;} .el-switch.is-disabled{opacity :1;}
.upgrade-wrap{
border:1px solid #ddd;
margin-bottom:2rem
padding:1rem 0;
}
</style> </style>

View File

@@ -1,4 +1,4 @@
import { getSignInList, signInDel } from "@/apis/signin"; import { getSignInList, signInDel, setSignInIndex } from "@/apis/signin";
import { injectGlobalData } from "@/provide"; import { injectGlobalData } from "@/provide";
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
@@ -12,7 +12,7 @@ export const provideDevices = () => {
timer: 0, timer: 0,
page: { page: {
Request: { Request: {
Page: 1, Size: +(localStorage.getItem('ps') || '10'), Name: '', Ids: [] Page: 1, Size: +(localStorage.getItem('ps') || '10'), Name: '', Ids: [], Prop: '', Asc: true
}, },
Count: 0, Count: 0,
List: [] List: []
@@ -23,7 +23,6 @@ export const provideDevices = () => {
}); });
const _getSignList = () => { const _getSignList = () => {
getSignInList(devices.page.Request).then((res) => { getSignInList(devices.page.Request).then((res) => {
console.log(res);
devices.page.Request = res.Request; devices.page.Request = res.Request;
devices.page.Count = res.Count; devices.page.Count = res.Count;
for (let j in res.List) { for (let j in res.List) {
@@ -33,7 +32,7 @@ export const provideDevices = () => {
} }
devices.page.List = res.List.sort((a, b) => b.Connected - a.Connected); devices.page.List = res.List;
for (let i = 0; i < devices.page.List.length; i++) { for (let i = 0; i < devices.page.List.length; i++) {
queue.push(devices.page.List[i]); queue.push(devices.page.List[i]);
} }
@@ -112,7 +111,12 @@ export const provideDevices = () => {
devices.timer = 0; devices.timer = 0;
} }
const setSort = (ids) => {
return setSignInIndex(ids);
}
return { return {
devices, machineId, _getSignList, _getSignList1, handleDeviceEdit, handlePageChange, handlePageSizeChange, handleDel, clearDevicesTimeout devices, machineId, _getSignList, _getSignList1, handleDeviceEdit, handlePageChange, handlePageSizeChange, handleDel, clearDevicesTimeout,
setSort
} }
} }

View File

@@ -40,8 +40,13 @@ export const provideTunnel = () => {
const clearTunnelTimeout = () => { const clearTunnelTimeout = () => {
clearTimeout(tunnel.value.timer); clearTimeout(tunnel.value.timer);
} }
const sortTunnel = (asc) => {
return Object.values(tunnel.value.list).sort((a, b) => {
return asc ? a.RouteLevel - b.RouteLevel : b.RouteLevel - a.RouteLevel;
}).map(c => c.MachineId);
}
return { return {
tunnel, _getTunnelInfo, handleTunnelEdit, handleTunnelRefresh, clearTunnelTimeout tunnel, _getTunnelInfo, handleTunnelEdit, handleTunnelRefresh, clearTunnelTimeout, sortTunnel
} }
} }
export const useTunnel = () => { export const useTunnel = () => {

View File

@@ -27,13 +27,14 @@ export const provideTuntap = () => {
if (globalData.value.api.connected) { if (globalData.value.api.connected) {
getTuntapInfo(tuntap.value.hashcode.toString()).then((res) => { getTuntapInfo(tuntap.value.hashcode.toString()).then((res) => {
tuntap.value.hashcode = res.HashCode; tuntap.value.hashcode = res.HashCode;
console.log(res);
if (res.List) { if (res.List) {
for (let j in res.List) { for (let j in res.List) {
res.List[j].running = res.List[j].Status == 2; res.List[j].running = res.List[j].Status == 2;
res.List[j].loading = res.List[j].Status == 1; res.List[j].loading = res.List[j].Status == 1;
res.List[j].system = 'system'; res.List[j].system = 'system';
const systemStr = res.List[j].System.toLowerCase(); const systemStr = res.List[j].SystemInfo.toLowerCase();
res.List[j].systemDocker = systemStr.indexOf('docker') >= 0; res.List[j].systemDocker = systemStr.indexOf('docker') >= 0;
for (let jj in systems) { for (let jj in systems) {
@@ -81,8 +82,22 @@ export const provideTuntap = () => {
.filter(c => c.IP.indexOf(name) >= 0 || (c.LanIPs.filter(d => d.indexOf(name) >= 0).length > 0)) .filter(c => c.IP.indexOf(name) >= 0 || (c.LanIPs.filter(d => d.indexOf(name) >= 0).length > 0))
.map(c => c.MachineId); .map(c => c.MachineId);
} }
const sortTuntapIP = (asc) => {
return Object.values(tuntap.value.list).sort((a, b) => {
const arrA = a.IP.split('.').map(c => Number(c));
const arrB = b.IP.split('.').map(c => Number(c));
for (let i = 0; i < arrA.length; i++) {
if (arrA[i] != arrB[i]) {
return asc ? arrA[i] - arrB[i] : arrB[i] - arrA[i];
}
}
return 0;
}).map(c => c.MachineId);
}
return { return {
tuntap, _getTuntapInfo, handleTuntapEdit, handleTuntapRefresh, clearTuntapTimeout, getTuntapMachines tuntap, _getTuntapInfo, handleTuntapEdit, handleTuntapRefresh, clearTuntapTimeout, getTuntapMachines, sortTuntapIP
} }
} }

View File

@@ -3,6 +3,8 @@ using Microsoft.Extensions.DependencyInjection;
using System.Reflection; using System.Reflection;
using linker.startup; using linker.startup;
using linker.config; using linker.config;
using System.ServiceProcess;
using System.Diagnostics;
namespace linker namespace linker
{ {
@@ -10,8 +12,28 @@ namespace linker
{ {
static async Task Main(string[] args) static async Task Main(string[] args)
{ {
Run(args); if (Environment.UserInteractive == false && OperatingSystem.IsWindows())
await Helper.Await().ConfigureAwait(false); {
AppDomain.CurrentDomain.UnhandledException += (a, b) =>
{
};
string serviceDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
Directory.SetCurrentDirectory(serviceDirectory);
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service()
};
ServiceBase.Run(ServicesToRun);
}
else
{
Run(args);
await Helper.Await().ConfigureAwait(false);
}
} }
public static void Run(string[] args) public static void Run(string[] args)

View File

@@ -1,6 +1,6 @@
namespace linker.service namespace linker
{ {
partial class LinkerService partial class Service
{ {
/// <summary> /// <summary>
/// 必需的设计器变量。 /// 必需的设计器变量。

24
linker/Service.cs Normal file
View File

@@ -0,0 +1,24 @@

using System.ServiceProcess;
namespace linker
{
partial class Service : ServiceBase
{
public Service()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
Program.Run(args);
// TODO: 在此处添加代码以启动服务。
}
protected override void OnStop()
{
// TODO: 在此处添加代码以执行停止服务所需的关闭操作。
}
}
}

View File

@@ -25,8 +25,8 @@
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>linker</PackageReleaseNotes> <PackageReleaseNotes>linker</PackageReleaseNotes>
<AssemblyVersion>1.2.0.3</AssemblyVersion> <AssemblyVersion>1.2.0.4</AssemblyVersion>
<FileVersion>1.2.0.3</FileVersion> <FileVersion>1.2.0.4</FileVersion>
</PropertyGroup> </PropertyGroup>
@@ -65,5 +65,6 @@
<PackageReference Include="LiteDB" Version="5.0.17" /> <PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="MemoryPack" Version="1.10.0" /> <PackageReference Include="MemoryPack" Version="1.10.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -7,4 +7,9 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor> <DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Update="Service.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
</Project> </Project>

View File

@@ -70,6 +70,15 @@ namespace linker.plugins.signin
Payload = MemoryPackSerializer.Serialize(param.Content) Payload = MemoryPackSerializer.Serialize(param.Content)
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
public async Task SetIndex(ApiControllerParamsInfo param)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)SignInMessengerIds.SetIndex,
Payload = MemoryPackSerializer.Serialize(param.Content.DeJson<string[]>())
}).ConfigureAwait(false);
}
public async Task<SignInListResponseInfo> List(ApiControllerParamsInfo param) public async Task<SignInListResponseInfo> List(ApiControllerParamsInfo param)
{ {
SignInListRequestInfo request = param.Content.DeJson<SignInListRequestInfo>(); SignInListRequestInfo request = param.Content.DeJson<SignInListRequestInfo>();

View File

@@ -94,7 +94,6 @@ namespace linker.plugins.signin.messenger
public DateTime LastSignIn { get; set; } = DateTime.Now; public DateTime LastSignIn { get; set; } = DateTime.Now;
public Dictionary<string, string> Args { get; set; } = new Dictionary<string, string>(); public Dictionary<string, string> Args { get; set; } = new Dictionary<string, string>();
private IPEndPoint ip = new IPEndPoint(IPAddress.Any, 0); private IPEndPoint ip = new IPEndPoint(IPAddress.Any, 0);
[MemoryPackAllowSerialize] [MemoryPackAllowSerialize]
public IPEndPoint IP public IPEndPoint IP
@@ -132,6 +131,9 @@ namespace linker.plugins.signin.messenger
[MemoryPackIgnore, JsonIgnore, BsonIgnore] [MemoryPackIgnore, JsonIgnore, BsonIgnore]
public IConnection Connection { get; set; } public IConnection Connection { get; set; }
[MemoryPackIgnore, JsonIgnore, BsonIgnore]
public uint Index { get; set; } = 65535;
} }

View File

@@ -44,6 +44,27 @@ namespace linker.plugins.signin.messenger
connection.Write(MemoryPackSerializer.Serialize(info.MachineId)); connection.Write(MemoryPackSerializer.Serialize(info.MachineId));
} }
[MessengerId((ushort)SignInMessengerIds.SetIndex)]
public void SetIndex(IConnection connection)
{
string[] ids = MemoryPackSerializer.Deserialize<string[]>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache))
{
IEnumerable<SignCacheInfo> list = signCaching.Get(cache.GroupId);
foreach (var item in list)
{
item.Index = uint.MaxValue;
}
for (uint i = 0; i < ids.Length; i++)
{
SignCacheInfo item = list.FirstOrDefault(c => c.MachineId == ids[i]);
if (item != null)
item.Index = i;
}
}
}
[MessengerId((ushort)SignInMessengerIds.List)] [MessengerId((ushort)SignInMessengerIds.List)]
public void List(IConnection connection) public void List(IConnection connection)
@@ -51,15 +72,51 @@ namespace linker.plugins.signin.messenger
SignInListRequestInfo request = MemoryPackSerializer.Deserialize<SignInListRequestInfo>(connection.ReceiveRequestWrap.Payload.Span); SignInListRequestInfo request = MemoryPackSerializer.Deserialize<SignInListRequestInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache)) if (signCaching.TryGet(connection.Id, out SignCacheInfo cache))
{ {
IEnumerable<SignCacheInfo> list = signCaching.Get(cache.GroupId).OrderByDescending(c => c.MachineName).OrderByDescending(c => c.LastSignIn).OrderByDescending(c => c.Version).ToList(); IEnumerable<SignCacheInfo> list = signCaching.Get(cache.GroupId).Where(c => c.MachineId != cache.MachineId);
if (string.IsNullOrWhiteSpace(request.Name) == false) if (string.IsNullOrWhiteSpace(request.Name) == false)
{ {
list = list.Where(c => c.Version.Contains(request.Name) || c.IP.ToString().Contains(request.Name) || c.MachineName.Contains(request.Name) || request.Ids.Contains(c.MachineId)); list = list.Where(c => c.Version.Contains(request.Name) || c.IP.ToString().Contains(request.Name) || c.MachineName.Contains(request.Name) || request.Ids.Contains(c.MachineId));
} }
if (string.IsNullOrWhiteSpace(request.Prop) == false)
{
if (request.Asc)
{
switch (request.Prop)
{
case "MachineId":
list = list.OrderBy(c => c.MachineName);
break;
case "Version":
list = list.OrderBy(c => c.Version);
break;
default:
list = list.OrderBy(c => c.Index);
break;
}
}
else
{
switch (request.Prop)
{
case "MachineId":
list = list.OrderByDescending(c => c.MachineName);
break;
case "Version":
list = list.OrderByDescending(c => c.Version);
break;
default:
list = list.OrderByDescending(c => c.Index);
break;
}
}
}
int count = list.Count(); int count = list.Count();
list = list.Skip((request.Page - 1) * request.Size).Take(request.Size); list = list.Skip((request.Page - 1) * request.Size).Take(request.Size);
SignInListResponseInfo response = new SignInListResponseInfo { Request = request, Count = count, List = list.ToList() }; List<SignCacheInfo> result = [cache, .. list];
SignInListResponseInfo response = new SignInListResponseInfo { Request = request, Count = count, List = result };
connection.Write(MemoryPackSerializer.Serialize(response)); connection.Write(MemoryPackSerializer.Serialize(response));
} }
@@ -148,10 +205,6 @@ namespace linker.plugins.signin.messenger
/// </summary> /// </summary>
public int Size { get; set; } = 10; public int Size { get; set; } = 10;
/// <summary> /// <summary>
/// 所在分组
/// </summary>
public string GroupId { get; set; }
/// <summary>
/// 按名称搜索 /// 按名称搜索
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
@@ -159,6 +212,15 @@ namespace linker.plugins.signin.messenger
/// 按id获取 /// 按id获取
/// </summary> /// </summary>
public string[] Ids { get; set; } public string[] Ids { get; set; }
/// <summary>
/// 排序
/// </summary>
public bool Asc { get; set; }
/// <summary>
/// 排序字段
/// </summary>
public string Prop { get; set; }
} }
[MemoryPackable] [MemoryPackable]

View File

@@ -14,6 +14,8 @@
Ids = 8, Ids = 8,
Exists = 9, Exists = 9,
SetIndex = 10,
None = 99 None = 99
} }
} }

View File

@@ -177,10 +177,19 @@ namespace linker.plugins.tunnel
{ {
} }
} }
try
if (File.Exists("msquic.dll.temp")) {
if (File.Exists("msquic.dll.temp"))
{
File.Delete("msquic.dll.temp");
}
if (File.Exists("msquic-openssl.dll"))
{
File.Delete("msquic-openssl.dll");
}
}
catch (Exception)
{ {
File.Delete("msquic.dll.temp");
} }
} }
} }

View File

@@ -63,7 +63,7 @@ namespace linker.plugins.tunnel
serviceCollection.AddSingleton<ITunnelAdapter, TunnelAdapter>(); serviceCollection.AddSingleton<ITunnelAdapter, TunnelAdapter>();
LoggerHelper.Instance.Info($"tunnel route level getting."); LoggerHelper.Instance.Info($"tunnel route level getting.");
config.Data.Client.Tunnel.RouteLevel = NetworkHelper.GetRouteLevel(out List<IPAddress> ips); config.Data.Client.Tunnel.RouteLevel = NetworkHelper.GetRouteLevel(config.Data.Client.Server, out List<IPAddress> ips);
config.Data.Client.Tunnel.RouteIPs = ips.ToArray(); config.Data.Client.Tunnel.RouteIPs = ips.ToArray();
LoggerHelper.Instance.Warning($"route ips:{string.Join(",", ips.Select(c => c.ToString()))}"); LoggerHelper.Instance.Warning($"route ips:{string.Join(",", ips.Select(c => c.ToString()))}");
config.Data.Client.Tunnel.LocalIPs = NetworkHelper.GetIPV6().Concat(NetworkHelper.GetIPV4()).ToArray(); config.Data.Client.Tunnel.LocalIPs = NetworkHelper.GetIPV6().Concat(NetworkHelper.GetIPV4()).ToArray();

View File

@@ -15,6 +15,7 @@ namespace linker.plugins.tuntap
//网卡IP和局域网IP。不参与打洞 //网卡IP和局域网IP。不参与打洞
return new ExcludeIPItem[] { new ExcludeIPItem { IPAddress = runningConfig.Data.Tuntap.IP, Mask = 32 } } return new ExcludeIPItem[] { new ExcludeIPItem { IPAddress = runningConfig.Data.Tuntap.IP, Mask = 32 } }
.Concat(runningConfig.Data.Tuntap.LanIPs.Select((c, index) => new ExcludeIPItem { IPAddress = c, Mask = (byte)runningConfig.Data.Tuntap.Masks[index] })) .Concat(runningConfig.Data.Tuntap.LanIPs.Select((c, index) => new ExcludeIPItem { IPAddress = c, Mask = (byte)runningConfig.Data.Tuntap.Masks[index] }))
.Where(c=>c.IPAddress != null)
.ToArray(); .ToArray();
} }
} }

View File

@@ -117,7 +117,11 @@ namespace linker.plugins.tuntap
return true; return true;
} }
//更新网卡信息 /// <summary>
/// 更新网卡信息
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<bool> Update(ApiControllerParamsInfo param) public async Task<bool> Update(ApiControllerParamsInfo param)
{ {
TuntapInfo info = param.Content.DeJson<TuntapInfo>(); TuntapInfo info = param.Content.DeJson<TuntapInfo>();

View File

@@ -12,6 +12,7 @@ using linker.plugins.client;
using linker.plugins.messenger; using linker.plugins.messenger;
using linker.plugins.tuntap.config; using linker.plugins.tuntap.config;
using linker.tun; using linker.tun;
using linker.libs.extends;
namespace linker.plugins.tuntap namespace linker.plugins.tuntap
{ {
@@ -56,7 +57,7 @@ namespace linker.plugins.tuntap
{ {
Task.Run(() => Task.Run(() =>
{ {
NetworkHelper.GetRouteLevel(out routeIps); NetworkHelper.GetRouteLevel(config.Data.Client.Server, out routeIps);
NotifyConfig(); NotifyConfig();
CheckTuntapStatusTask(); CheckTuntapStatusTask();
if (runningConfig.Data.Tuntap.Running) if (runningConfig.Data.Tuntap.Running)
@@ -77,19 +78,17 @@ namespace linker.plugins.tuntap
} }
Task.Run(() => Task.Run(() =>
{ {
NotifyConfig(); SetupBefore();
try try
{ {
if (runningConfig.Data.Tuntap.IP.Equals(IPAddress.Any)) if (runningConfig.Data.Tuntap.IP.Equals(IPAddress.Any))
{ {
return; return;
} }
linkerTunDeviceAdapter.Setup(runningConfig.Data.Tuntap.IP, 24, 1416); linkerTunDeviceAdapter.Setup(runningConfig.Data.Tuntap.IP, runningConfig.Data.Tuntap.PrefixLength, 1416);
if (string.IsNullOrWhiteSpace(linkerTunDeviceAdapter.Error)) if (string.IsNullOrWhiteSpace(linkerTunDeviceAdapter.Error))
{ {
linkerTunDeviceAdapter.SetNat(); SetupSuccess();
runningConfig.Data.Tuntap.Running = true;
runningConfig.Data.Update();
} }
else else
{ {
@@ -102,11 +101,28 @@ namespace linker.plugins.tuntap
} }
finally finally
{ {
Interlocked.Exchange(ref operating, 0); SetupAfter();
NotifyConfig();
} }
}); });
} }
private void SetupBefore()
{
NotifyConfig();
}
private void SetupAfter()
{
Interlocked.Exchange(ref operating, 0);
NotifyConfig();
}
private void SetupSuccess()
{
linkerTunDeviceAdapter.SetNat();
AddForward();
runningConfig.Data.Tuntap.Running = true;
runningConfig.Data.Update();
}
/// <summary> /// <summary>
/// 停止网卡 /// 停止网卡
/// </summary> /// </summary>
@@ -118,11 +134,9 @@ namespace linker.plugins.tuntap
} }
try try
{ {
NotifyConfig(); ShutdownBefore();
linkerTunDeviceAdapter.Shutdown(); linkerTunDeviceAdapter.Shutdown();
ShutdownSuccess();
runningConfig.Data.Tuntap.Running = false;
runningConfig.Data.Update();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -133,10 +147,27 @@ namespace linker.plugins.tuntap
} }
finally finally
{ {
Interlocked.Exchange(ref operating, 0); ShutdownAfter();
NotifyConfig();
} }
} }
private void ShutdownBefore()
{
NotifyConfig();
}
private void ShutdownAfter()
{
Interlocked.Exchange(ref operating, 0);
NotifyConfig();
}
private void ShutdownSuccess()
{
linkerTunDeviceAdapter.RemoveNat();
DeleteForward();
runningConfig.Data.Tuntap.Running = false;
runningConfig.Data.Update();
}
/// <summary> /// <summary>
/// 刷新信息,把自己的网卡配置发给别人,顺便把别人的网卡信息带回来 /// 刷新信息,把自己的网卡配置发给别人,顺便把别人的网卡信息带回来
@@ -153,10 +184,14 @@ namespace linker.plugins.tuntap
{ {
Task.Run(() => Task.Run(() =>
{ {
DeleteForward();
runningConfig.Data.Tuntap.IP = info.IP; runningConfig.Data.Tuntap.IP = info.IP;
runningConfig.Data.Tuntap.LanIPs = info.LanIPs; runningConfig.Data.Tuntap.LanIPs = info.LanIPs;
runningConfig.Data.Tuntap.Masks = info.Masks; runningConfig.Data.Tuntap.Masks = info.Masks;
runningConfig.Data.Tuntap.PrefixLength = info.PrefixLength;
runningConfig.Data.Tuntap.Gateway = info.Gateway; runningConfig.Data.Tuntap.Gateway = info.Gateway;
runningConfig.Data.Tuntap.Upgrade = info.Upgrade;
runningConfig.Data.Tuntap.Forwards = info.Forwards;
runningConfig.Data.Update(); runningConfig.Data.Update();
if (Status == TuntapStatus.Running) if (Status == TuntapStatus.Running)
{ {
@@ -165,6 +200,7 @@ namespace linker.plugins.tuntap
} }
else else
{ {
AddForward();
NotifyConfig(); NotifyConfig();
} }
}); });
@@ -204,9 +240,9 @@ namespace linker.plugins.tuntap
{ {
tuntapInfos.AddOrUpdate(item.MachineId, item, (a, b) => item); tuntapInfos.AddOrUpdate(item.MachineId, item, (a, b) => item);
} }
Interlocked.Increment(ref infosVersion);
AddRoute(); AddRoute();
} }
Interlocked.Increment(ref infosVersion);
}); });
} }
/// <summary> /// <summary>
@@ -220,11 +256,15 @@ namespace linker.plugins.tuntap
IP = runningConfig.Data.Tuntap.IP, IP = runningConfig.Data.Tuntap.IP,
LanIPs = runningConfig.Data.Tuntap.LanIPs, LanIPs = runningConfig.Data.Tuntap.LanIPs,
Masks = runningConfig.Data.Tuntap.Masks, Masks = runningConfig.Data.Tuntap.Masks,
PrefixLength = runningConfig.Data.Tuntap.PrefixLength,
MachineId = config.Data.Client.Id, MachineId = config.Data.Client.Id,
Status = Status, Status = Status,
Error = linkerTunDeviceAdapter.Error, Error = linkerTunDeviceAdapter.Error,
System = $"{System.Runtime.InteropServices.RuntimeInformation.OSDescription} {(string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SNLTTY_LINKER_IS_DOCKER")) == false ? "Docker" : "")}", Error1 = linkerTunDeviceAdapter.Error1,
SystemInfo = $"{System.Runtime.InteropServices.RuntimeInformation.OSDescription} {(string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SNLTTY_LINKER_IS_DOCKER")) == false ? "Docker" : "")}",
Gateway = runningConfig.Data.Tuntap.Gateway, Gateway = runningConfig.Data.Tuntap.Gateway,
Upgrade = runningConfig.Data.Tuntap.Upgrade,
Forwards = runningConfig.Data.Tuntap.Forwards,
}; };
if (runningConfig.Data.Tuntap.Masks.Length != runningConfig.Data.Tuntap.LanIPs.Length) if (runningConfig.Data.Tuntap.Masks.Length != runningConfig.Data.Tuntap.LanIPs.Length)
{ {
@@ -258,16 +298,38 @@ namespace linker.plugins.tuntap
return infos; return infos;
} }
// <summary>
/// 添加端口转发
/// </summary>
private void AddForward()
{
linkerTunDeviceAdapter.AddForward(runningConfig.Data.Tuntap.Forwards.Select(c => new LinkerTunDeviceForwardItem { ListenAddr = c.ListenAddr, ListenPort = c.ListenPort, ConnectAddr = c.ConnectAddr, ConnectPort = c.ConnectPort }).ToList());
}
/// <summary>
/// 删除端口转发
/// </summary>
private void DeleteForward()
{
linkerTunDeviceAdapter.RemoveForward(runningConfig.Data.Tuntap.Forwards.Select(c => new LinkerTunDeviceForwardItem { ListenAddr = c.ListenAddr, ListenPort = c.ListenPort, ConnectAddr = c.ConnectAddr, ConnectPort = c.ConnectPort }).ToList());
}
/// <summary> /// <summary>
/// 删除路由 /// 删除路由
/// </summary> /// </summary>
private void DelRoute() private void DelRoute()
{ {
List<TuntapVeaLanIPAddressList> ipsList = ParseIPs(tuntapInfos.Values.ToList()); try
TuntapVeaLanIPAddress[] ips = ipsList.SelectMany(c => c.IPS).ToArray(); {
List<TuntapVeaLanIPAddressList> ipsList = ParseIPs(tuntapInfos.Values.ToList());
var items = ipsList.SelectMany(c => c.IPS).Select(c => new LinkerTunDeviceRouteItem { Address = c.OriginIPAddress, PrefixLength = c.MaskLength }).ToArray(); TuntapVeaLanIPAddress[] ips = ipsList.SelectMany(c => c.IPS).ToArray();
linkerTunDeviceAdapter.DelRoute(items, runningConfig.Data.Tuntap.Gateway); var items = ipsList.SelectMany(c => c.IPS).Select(c => new LinkerTunDeviceRouteItem { Address = c.OriginIPAddress, PrefixLength = c.MaskLength }).ToArray();
linkerTunDeviceAdapter.DelRoute(items, runningConfig.Data.Tuntap.Gateway);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
} }
/// <summary> /// <summary>
/// 添加路由 /// 添加路由
@@ -291,7 +353,7 @@ namespace linker.plugins.tuntap
{ {
uint[] localIps = NetworkHelper.GetIPV4() uint[] localIps = NetworkHelper.GetIPV4()
.Concat(new IPAddress[] { runningConfig.Data.Tuntap.IP }) .Concat(new IPAddress[] { runningConfig.Data.Tuntap.IP })
.Concat(runningConfig.Data.Tuntap.LanIPs) .Concat(runningConfig.Data.Tuntap.LanIPs.Where(c => c != null))
.Concat(routeIps) .Concat(routeIps)
.Select(c => BinaryPrimitives.ReadUInt32BigEndian(c.GetAddressBytes())) .Select(c => BinaryPrimitives.ReadUInt32BigEndian(c.GetAddressBytes()))
.ToArray(); .ToArray();
@@ -313,7 +375,7 @@ namespace linker.plugins.tuntap
private List<TuntapVeaLanIPAddress> ParseIPs(IPAddress[] lanIPs, int[] masks) private List<TuntapVeaLanIPAddress> ParseIPs(IPAddress[] lanIPs, int[] masks)
{ {
if (masks.Length != lanIPs.Length) masks = lanIPs.Select(c => 24).ToArray(); if (masks.Length != lanIPs.Length) masks = lanIPs.Select(c => 24).ToArray();
return lanIPs.Where(c => c.Equals(IPAddress.Any) == false).Select((c, index) => return lanIPs.Where(c => c.Equals(IPAddress.Any) == false && c != null).Select((c, index) =>
{ {
return ParseIPAddress(c, (byte)masks[index]); return ParseIPAddress(c, (byte)masks[index]);

View File

@@ -1,25 +1,15 @@
using linker.plugins.tuntap.config; using linker.plugins.tuntap.config;
using MemoryPack; using MemoryPack;
using System.Buffers.Binary;
using System.Net; using System.Net;
namespace linker.plugins.tuntap.config namespace linker.plugins.tuntap.config
{ {
public sealed class TuntapConfigInfo public sealed class TuntapConfigInfo
{ {
private IPAddress ip = IPAddress.Any;
/// <summary> /// <summary>
/// 网卡IP /// 网卡IP
/// </summary> /// </summary>
public IPAddress IP public IPAddress IP { get; set; } = IPAddress.Any;
{
get => ip; set
{
ip = value;
IpInt = BinaryPrimitives.ReadUInt32BigEndian(ip.GetAddressBytes());
}
}
public uint IpInt { get; private set; }
/// <summary> /// <summary>
/// 局域网IP列表 /// 局域网IP列表
/// </summary> /// </summary>
@@ -28,12 +18,28 @@ namespace linker.plugins.tuntap.config
/// 局域网掩码列表与IP列表一一对应 /// 局域网掩码列表与IP列表一一对应
/// </summary> /// </summary>
public int[] Masks { get; set; } = Array.Empty<int>(); public int[] Masks { get; set; } = Array.Empty<int>();
/// <summary>
/// 前缀长度
/// </summary>
public byte PrefixLength { get; set; } = 24;
/// <summary> /// <summary>
/// 是否在运行中 /// 是否在运行中
/// </summary> /// </summary>
public bool Running { get; set; } public bool Running { get; set; }
/// <summary>
/// 是否网关
/// </summary>
public bool Gateway { get; set; } public bool Gateway { get; set; }
/// <summary>
/// 使用高级功能
/// </summary>
public bool Upgrade { get; set; }
/// <summary>
/// 端口转发列表
/// </summary>
public List<TuntapForwardInfo> Forwards { get; set; } = new List<TuntapForwardInfo>();
} }
@@ -65,31 +71,93 @@ namespace linker.plugins.tuntap.config
public enum TuntapStatus : byte public enum TuntapStatus : byte
{ {
/// <summary>
/// 无
/// </summary>
Normal = 0, Normal = 0,
/// <summary>
/// 操作中
/// </summary>
Operating = 1, Operating = 1,
/// <summary>
/// 运行中
/// </summary>
Running = 2 Running = 2
} }
[MemoryPackable] [MemoryPackable]
public sealed partial class TuntapInfo public sealed partial class TuntapInfo
{ {
/// <summary>
/// 设备id
/// </summary>
public string MachineId { get; set; } public string MachineId { get; set; }
/// <summary>
/// 虚拟网卡状态
/// </summary>
public TuntapStatus Status { get; set; } public TuntapStatus Status { get; set; }
/// <summary>
/// 虚拟网卡IP
/// </summary>
[MemoryPackAllowSerialize] [MemoryPackAllowSerialize]
public IPAddress IP { get; set; } public IPAddress IP { get; set; }
/// <summary>
/// 局域网IP
/// </summary>
[MemoryPackAllowSerialize] [MemoryPackAllowSerialize]
public IPAddress[] LanIPs { get; set; } = Array.Empty<IPAddress>(); public IPAddress[] LanIPs { get; set; } = Array.Empty<IPAddress>();
/// <summary>
/// 局域网IP掩码
/// </summary>
public int[] Masks { get; set; } = Array.Empty<int>(); public int[] Masks { get; set; } = Array.Empty<int>();
public string Error { get; set; }
public string System { get; set; }
/// <summary>
/// 前缀长度
/// </summary>
public byte PrefixLength { get; set; } = 24;
/// <summary>
/// 网卡安装错误
/// </summary>
public string Error { get; set; }
/// <summary>
/// NAT设置错误
/// </summary>
public string Error1 { get; set; }
/// <summary>
/// 系统信息
/// </summary>
public string SystemInfo { get; set; }
/// <summary>
/// 是否网关
/// </summary>
public bool Gateway { get; set; } public bool Gateway { get; set; }
/// <summary>
/// 使用高级功能
/// </summary>
public bool Upgrade { get; set; }
/// <summary>
/// 端口转发列表
/// </summary>
public List<TuntapForwardInfo> Forwards { get; set; } = new List<TuntapForwardInfo>();
} }
[MemoryPackable]
public sealed partial class TuntapForwardInfo
{
[MemoryPackAllowSerialize]
public IPAddress ListenAddr { get; set; } = IPAddress.Any;
public int ListenPort { get; set; }
[MemoryPackAllowSerialize]
public IPAddress ConnectAddr { get; set; } = IPAddress.Any;
public int ConnectPort { get; set; }
}
} }

View File

@@ -156,6 +156,8 @@ namespace linker.plugins.tuntap.messenger
})); }));
} }
Task.WhenAll(tasks).ContinueWith(async (result) => Task.WhenAll(tasks).ContinueWith(async (result) =>
{ {
List<TuntapInfo> results = tasks.Where(c => c.Result.Code == MessageResponeCodes.OK) List<TuntapInfo> results = tasks.Where(c => c.Result.Code == MessageResponeCodes.OK)

View File

@@ -12,12 +12,11 @@ call npm run build
cd ../ cd ../
for %%r in (win-x64,win-arm64) do ( for %%r in (win-x64,win-arm64) do (
dotnet publish ./linker.service -c release -f net8.0 -o public/extends/%%r/linker-%%r/ -r %%r -p:PublishAot=true -p:PublishTrimmed=true --self-contained true -p:TieredPGO=true -p:DebugType=none -p:DebugSymbols=false -p:EnableCompressionInSingleFile=true -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:HttpActivityPropagationSupport=false -p:InvariantGlobalization=true -p:MetadataUpdaterSupport=false -p:UseSystemResourceKeys=true
echo F|xcopy "linker.tray.win\\dist\\*" "public\\extends\\%%r\\linker-%%r\\*" /s /f /h /y echo F|xcopy "linker.tray.win\\dist\\*" "public\\extends\\%%r\\linker-%%r\\*" /s /f /h /y
echo F|xcopy "linker\\msquic.dll" "public\\extends\\%%r\\linker-%%r\\msquic.dll" /s /f /h /y echo F|xcopy "linker\\msquic.dll" "public\\extends\\%%r\\linker-%%r\\msquic.dll" /s /f /h /y
echo F|xcopy "linker\\msquic-%%r.dll" "public\\extends\\%%r\\linker-%%r\\msquic.dll" /s /f /h /y echo F|xcopy "linker\\msquic-%%r.dll" "public\\extends\\%%r\\linker-%%r\\msquic.dll" /s /f /h /y
echo F|xcopy "linker\\msquic-openssl3-%%r.dll" "public\\extends\\%%r\\linker-%%r\\msquic-openssl.dll" /s /f /h /y echo F|xcopy "linker\\msquic-openssl3-%%r.dll" "public\\extends\\%%r\\linker-%%r\\msquic-openssl.dll" /s /f /h /y
echo F|xcopy "linker\\wintun-%%r.dll" "public\\extends\\%%r\\linker-%%r\\wintun.dll" /s /f /h /y echo F|xcopy "linker\\wintun-%%r.dll" "public\\extends\\%%r\\linker-%%r\\wintun.dll" /s /f /h /y
) )
for %%r in (win-x64,win-arm64,linux-x64,linux-arm64,linux-musl-x64,linux-musl-arm64,osx-x64,osx-arm64) do ( for %%r in (win-x64,win-arm64,linux-x64,linux-arm64,linux-musl-x64,linux-musl-arm64,osx-x64,osx-arm64) do (