虚拟网卡ping值

This commit is contained in:
snltty
2024-08-12 17:02:54 +08:00
parent 06bfe7d344
commit 2750bf44b7
18 changed files with 400 additions and 138 deletions

View File

@@ -48,16 +48,24 @@ jobs:
env: env:
GITHUB_TOKEN: '${{ secrets.ACTIONS_TOKEN }}' GITHUB_TOKEN: '${{ secrets.ACTIONS_TOKEN }}'
with: with:
tag_name: v1.2.0.5 tag_name: v1.2.0.6
release_name: v1.2.0.5.${{ steps.date.outputs.today }} release_name: v1.2.0.6.${{ steps.date.outputs.today }}
draft: false draft: false
prerelease: false prerelease: false
body: | body: |
1. UDP端口映射连接(无ssl加密) 1. 显示虚拟网卡ping值
2. UDP打洞纯净版(无ssl加密) 2. 更新服务器,更新服务器,更新服务器
3. 如果当前版本小于v1.2.0.4则windows下需要重新卸载安装服务
4. 先更新所有客户端,再更新服务端(先更新服务端会获取不到客户端列表) #- name: upload win x86 oss
5. 用不上无加密UDP打洞的可以不更新 # id: upload-win-x86-oss
# uses: tvrcgo/oss-action@v0.1.1
# with:
# region: oss-cn-guangzhou
# key-id: '${{ secrets.ALIYUN_OSS_ID }}'
# key-secret: '${{ secrets.ALIYUN_OSS_SECRET }}'
# bucket: snltty
# asset-path: ./public/publish-zip/linker-win-x86.zip
# target-path: /linker/v1.2.0.6/linker-win-x86.zip
- name: upload win x86 - name: upload win x86
id: upload-win-x86 id: upload-win-x86

View File

@@ -38,8 +38,21 @@ sudo pfctl -f /etc/pf.conf -e
:::tip[2、情况2你的设备无法使用NAT转发时] :::tip[2、情况2你的设备无法使用NAT转发时]
1. 你的设备无法使用NAT转发(一般出现在低版本windows下win10以下),那你只能使用windows的端口转发功能来访问你当前设备局域网下的其它设备 1. 你的设备无法使用NAT转发(一般出现在低版本windows下win10以下),那你只能使用端口转发功能来访问你当前设备局域网下的其它设备
2. 按如下配置。当其它设备通过`192.168.54.2:12345` 访问时,将访问到你的局域网的`192.168.1.35:3389` 2. 按如下配置。当其它设备通过`192.168.54.2:12345` 访问时,将访问到你的局域网的`192.168.1.35:3389`
3. macos下需要你自己在**被访问端**添加端口转发
```
//编辑 pf 配置文件
sudo nano /etc/pf.conf
//添加转发规则
rdr pass on en0 inet proto tcp from any to any port 33890 -> 127.0.0.1 port 3389
rdr pass on en0 inet proto udp from any to any port 33890 -> 127.0.0.1 port 3389
//启用并重新加载 pf
sudo pfctl -f /etc/pf.conf
sudo pfctl -e
```
![Docusaurus Plushie](./img/tun-forward.png) ![Docusaurus Plushie](./img/tun-forward.png)

View File

@@ -0,0 +1,35 @@
using System.Collections.Concurrent;
using System.Reflection.PortableExecutable;
using System.Threading;
namespace linker.libs
{
public sealed class OperatingManager
{
private uint operating = 0;
public bool Operating => operating == 1;
public bool StartOperation()
{
return Interlocked.CompareExchange(ref operating, 1, 0) == 0;
}
public void StopOperation()
{
Interlocked.Exchange(ref operating, 0);
}
}
public sealed class OperatingMultipleManager
{
private readonly ConcurrentDictionary<string, bool> dicOperating = new ConcurrentDictionary<string, bool>();
public bool StartOperation(string key)
{
return dicOperating.TryAdd(key, true);
}
public void StopOperation(string key)
{
dicOperating.TryRemove(key, out _);
}
}
}

View File

@@ -0,0 +1,21 @@

using System.Threading;
namespace linker.libs
{
public sealed class VersionManager
{
private ulong version = 0;
public bool Eq(ulong outsideVersion, out ulong insideVersion)
{
insideVersion = version;
return outsideVersion == version;
}
public void Add()
{
Interlocked.Increment(ref version);
}
}
}

View File

@@ -1,6 +1,7 @@
using linker.libs; using linker.libs;
using linker.libs.extends; using linker.libs.extends;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
using System.Linq;
using System.Net; using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -131,7 +132,7 @@ namespace linker.tun
try try
{ {
IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.MaskValue(prefixLength)); IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.MaskValue(prefixLength));
CommandHelper.Linux(string.Empty, new string[] { CommandHelper.Linux(string.Empty, new string[] {
$"sysctl -w net.ipv4.ip_forward=1", $"sysctl -w net.ipv4.ip_forward=1",
$"iptables -t nat -A POSTROUTING ! -o {Name} -s {network}/{prefixLength} -j MASQUERADE", $"iptables -t nat -A POSTROUTING ! -o {Name} -s {network}/{prefixLength} -j MASQUERADE",
}); });
@@ -165,9 +166,33 @@ namespace linker.tun
public void AddForward(List<LinkerTunDeviceForwardItem> forwards) public void AddForward(List<LinkerTunDeviceForwardItem> forwards)
{ {
string[] commands = forwards.Where(c => c != null && c.Enable).SelectMany(c =>
{
return new string[] {
$"sysctl -w net.ipv4.ip_forward=1",
$"iptables -t nat -A PREROUTING -p tcp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -A POSTROUTING -p tcp --dport {c.ConnectPort} -j MASQUERADE",
$"iptables -t nat -A PREROUTING -p udp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -A POSTROUTING -p udp --dport {c.ConnectPort} -j MASQUERADE",
};
}).ToArray();
CommandHelper.Windows(string.Empty, commands);
} }
public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards) public void RemoveForward(List<LinkerTunDeviceForwardItem> forwards)
{ {
string[] commands = forwards.Where(c => c != null && c.Enable).SelectMany(c =>
{
return new string[] {
$"sysctl -w net.ipv4.ip_forward=1",
$"iptables -t nat -D PREROUTING -p tcp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -D POSTROUTING -p tcp --dport {c.ConnectPort} -j MASQUERADE",
$"iptables -t nat -D PREROUTING -p udp --dport {c.ListenPort} -j DNAT --to-destination {c.ConnectAddr}:{c.ConnectPort}",
$"iptables -t nat -D POSTROUTING -p udp --dport {c.ConnectPort} -j MASQUERADE"
};
}).ToArray();
CommandHelper.Windows(string.Empty, commands);
} }
@@ -287,7 +312,7 @@ namespace linker.tun
{ {
} }
} }

View File

@@ -13,7 +13,7 @@ namespace linker.tunnel.transport
{ {
public string Name => "udp"; public string Name => "udp";
public string Label => "UDP、非常纯"; public string Label => "UDP、非常纯无ssl";
public TunnelProtocolType ProtocolType => TunnelProtocolType.Udp; public TunnelProtocolType ProtocolType => TunnelProtocolType.Udp;
public TunnelWanPortProtocolType AllowWanPortProtocolType => TunnelWanPortProtocolType.Udp; public TunnelWanPortProtocolType AllowWanPortProtocolType => TunnelWanPortProtocolType.Udp;

View File

@@ -17,7 +17,7 @@ namespace linker.tunnel.transport
{ {
public string Name => "UdpPortMap"; public string Name => "UdpPortMap";
public string Label => "UDP、端口映射"; public string Label => "UDP、端口映射无ssl";
public TunnelProtocolType ProtocolType => TunnelProtocolType.Udp; public TunnelProtocolType ProtocolType => TunnelProtocolType.Udp;

View File

@@ -55,6 +55,14 @@
</template> </template>
</div> </div>
</template> </template>
<template v-if="showDelay">
<template v-if="tuntap.list[scope.row.MachineId].Delay>=0 && tuntap.list[scope.row.MachineId].Delay<=100">
<div class="delay green">{{ tuntap.list[scope.row.MachineId].Delay }}ms</div>
</template>
<template>
<div class="delay yellow">{{ tuntap.list[scope.row.MachineId].Delay }}ms</div>
</template>
</template>
</div> </div>
</div> </div>
</template> </template>
@@ -65,12 +73,17 @@ import { stopTuntap, runTuntap } from '@/apis/tuntap';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { useTuntap } from './tuntap'; import { useTuntap } from './tuntap';
import {Loading} from '@element-plus/icons-vue' import {Loading} from '@element-plus/icons-vue'
import { injectGlobalData } from '@/provide';
import { computed } from 'vue';
export default { export default {
emits: ['edit','refresh'], emits: ['edit','refresh'],
components:{Loading}, components:{Loading},
setup(props, { emit }) { setup(props, { emit }) {
const tuntap = useTuntap(); const tuntap = useTuntap();
const globalData = injectGlobalData();
const showDelay = computed(()=>globalData.value.config.Running.Tuntap.ShowDelay);
const handleTuntap = (tuntap) => { const handleTuntap = (tuntap) => {
const fn = tuntap.running ? stopTuntap (tuntap.MachineId) : runTuntap(tuntap.MachineId); const fn = tuntap.running ? stopTuntap (tuntap.MachineId) : runTuntap(tuntap.MachineId);
tuntap.loading = true; tuntap.loading = true;
@@ -88,7 +101,7 @@ export default {
} }
return { return {
tuntap, handleTuntap, handleTuntapIP,handleTuntapRefresh tuntap,showDelay, handleTuntap, handleTuntapIP,handleTuntapRefresh
} }
} }
} }
@@ -119,5 +132,7 @@ export default {
-webkit-text-fill-color:hsla(0,0%,100%,0); -webkit-text-fill-color:hsla(0,0%,100%,0);
} }
} }
.delay{position: absolute;right:0;bottom:0;line-height:normal}
</style> </style>

View File

@@ -3,26 +3,41 @@
<div> <div>
<el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="140"> <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">
赐予此设备IP其它可以通过这个IP访问 赐予此设备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" /> / <el-input @change="handlePrefixLengthChange" v-model="state.ruleForm.PrefixLength" style="width:4rem" /> <el-input v-model="state.ruleForm.IP" style="width:14rem" />
<span>/</span>
<el-input @change="handlePrefixLengthChange" v-model="state.ruleForm.PrefixLength" style="width:4rem" />
<span style="width: 3rem;"></span>
<el-checkbox v-model="state.ruleForm.ShowDelay" label="显示延迟" size="large" />
<el-popover
placement="top" title="提示" :width="400" trigger="hover"
content="在测试延迟时如果未连接将自动去打洞连接当你有一百个设备时每个设备都同时去与其它99台设备连接这数据量不小所以尽量不要个设备都勾选"
>
<template #reference>
<el-checkbox v-model="state.ruleForm.AutoConnect" label="自动连接?" size="large" />
</template>
</el-popover>
</el-form-item> </el-form-item>
<el-form-item prop="upgrade" style="margin-bottom:0"> <el-form-item prop="upgrade" style="margin-bottom:0">
<el-checkbox v-model="state.ruleForm.Upgrade" label="我很懂,我要使用高级功能(点对网和网对网)" size="large" /> <el-checkbox v-model="state.ruleForm.Upgrade" label="我很懂,我要使用高级功能(点对网和网对网)" size="large" />
</el-form-item> </el-form-item>
<div class="upgrade-wrap" v-if="state.ruleForm.Upgrade"> <div class="upgrade-wrap" v-if="state.ruleForm.Upgrade">
<el-form-item prop="gateway" style="margin-bottom:0"> <el-form-item prop="gateway" style="border-bottom: 1px solid #ddd;margin-bottom:0">
<el-checkbox v-model="state.ruleForm.Gateway" label="此设备在路由器(网对网,将对方的 局域网IP 转发到对方)" size="large" /> <el-checkbox v-model="state.ruleForm.Gateway" label="此设备在路由器(网对网)" size="large" />
</el-form-item> </el-form-item>
<el-form-item prop="gateway" style="margin-bottom:0"> <el-form-item prop="nat" style="margin-bottom:0">
<span class="yellow">此设备可以用NAT转发那填写局域网IP其它交给NAT(linuxmacoswin10+)</span> <span class="yellow">此设备能使用NAT转发只需局域网IP剩下的交给NAT(linuxmacoswin10+)</span>
</el-form-item> </el-form-item>
<el-form-item label="此设备局域网IP" prop="LanIP"> <el-form-item label="此设备局域网IP" prop="LanIP" style="border-bottom: 1px solid #ddd;margin-bottom:0">
<template v-for="(item, index) in state.ruleForm.LanIPs" :key="index"> <template v-for="(item, index) in state.ruleForm.LanIPs" :key="index">
<div class="flex" style="margin-bottom:.6rem"> <div class="flex" style="margin-bottom:.6rem">
<div class="flex-1"> <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" /> <el-input v-model="state.ruleForm.LanIPs[index]" style="width:14rem" />
<span>/</span>
<el-input @change="handleMaskChange(index)" v-model="state.ruleForm.Masks[index]" style="width:4rem" />
</div> </div>
<div class="pdl-10"> <div class="pdl-10">
<el-button type="danger" @click="handleDel(index)"><el-icon><Delete /></el-icon></el-button> <el-button type="danger" @click="handleDel(index)"><el-icon><Delete /></el-icon></el-button>
@@ -31,15 +46,15 @@
</div> </div>
</template> </template>
</el-form-item> </el-form-item>
<el-form-item prop="gateway" style="margin-bottom:0"> <el-form-item prop="forward" style="margin-bottom:0">
<span class="yellow">此设备不能用NAT转发那可以使用系统端口转发实现类似的效果(仅windows)</span> <span class="yellow">此设备无法使用NAT转发或只想使用端口转发</span>
</el-form-item> </el-form-item>
<el-form-item label="端口转发" prop="foreards"> <el-form-item label="端口转发" prop="forwards">
<template v-for="(item, index) in state.ruleForm.Forwards" :key="index"> <template v-for="(item, index) in state.ruleForm.Forwards" :key="index">
<div class="flex" style="margin-bottom:.6rem"> <div class="flex" style="margin-bottom:.6rem">
<div class="flex-1"> <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.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" /> -> <el-input v-model="item.ConnectAddr" style="width:14rem" /> : <el-input @change="handleForwardChange(index)" v-model="item.ConnectPort" style="width:6rem" />
</div> </div>
<div class="pdl-10"> <div class="pdl-10">
@@ -85,6 +100,8 @@ export default {
Masks: tuntap.value.current.Masks.slice(0), Masks: tuntap.value.current.Masks.slice(0),
PrefixLength:tuntap.value.current.PrefixLength || 24, PrefixLength:tuntap.value.current.PrefixLength || 24,
Gateway: tuntap.value.current.Gateway, Gateway: tuntap.value.current.Gateway,
ShowDelay: tuntap.value.current.ShowDelay,
AutoConnect: tuntap.value.current.AutoConnect,
Upgrade: tuntap.value.current.Upgrade, Upgrade: tuntap.value.current.Upgrade,
Forwards:tuntap.value.current.Forwards.length == 0 ? [ Forwards:tuntap.value.current.Forwards.length == 0 ? [
@@ -144,6 +161,8 @@ export default {
json.Masks = masks; json.Masks = masks;
json.PrefixLength = +state.ruleForm.PrefixLength; json.PrefixLength = +state.ruleForm.PrefixLength;
json.Gateway = state.ruleForm.Gateway; json.Gateway = state.ruleForm.Gateway;
json.ShowDelay = state.ruleForm.ShowDelay;
json.AutoConnect = state.ruleForm.AutoConnect;
json.Upgrade = state.ruleForm.Upgrade; json.Upgrade = state.ruleForm.Upgrade;
json.Forwards = state.ruleForm.Forwards; json.Forwards = state.ruleForm.Forwards;
json.Forwards.forEach(c=>{ json.Forwards.forEach(c=>{
@@ -185,6 +204,6 @@ export default {
.upgrade-wrap{ .upgrade-wrap{
border:1px solid #ddd; border:1px solid #ddd;
margin-bottom:2rem margin-bottom:2rem
padding:1rem 0; padding:0 0 1rem 0;
} }
</style> </style>

View File

@@ -55,7 +55,6 @@ export default {
bufferSize:globalData.value.bufferSize bufferSize:globalData.value.bufferSize
}); });
watch(()=>globalData.value.config.Running.Tunnel.Transports,()=>{ watch(()=>globalData.value.config.Running.Tunnel.Transports,()=>{
console.log(globalData.value.config.Running.Tunnel.Transports);
state.list = globalData.value.config.Running.Tunnel.Transports.sort((a,b)=>a.Order - b.Order); state.list = globalData.value.config.Running.Tunnel.Transports.sort((a,b)=>a.Order - b.Order);
}); });

View File

@@ -155,7 +155,12 @@ namespace linker.plugins.relay
var servers = running.Data.Relay.Servers var servers = running.Data.Relay.Servers
.Where(c => c.Disabled == false) .Where(c => c.Disabled == false)
.Where(c => string.IsNullOrWhiteSpace(c.Host) == false) .Where(c => string.IsNullOrWhiteSpace(c.Host) == false)
.Where(c => c.Delay >= 0).OrderBy(c => c.Delay); .Where(c => c.Delay >= 0);
if (running.Data.Relay.ByRelay)
{
servers = servers.OrderBy(c => c.Delay);
}
foreach (RelayServerInfo item in servers) foreach (RelayServerInfo item in servers)
{ {
ITransport transport = transports.FirstOrDefault(c => c.Type == item.RelayType); ITransport transport = transports.FirstOrDefault(c => c.Type == item.RelayType);
@@ -278,43 +283,20 @@ namespace linker.plugins.relay
{ {
try try
{ {
var tasks = running.Data.Relay.Servers.Select(c => foreach (var server in running.Data.Relay.Servers)
{ {
try ITransport transport = transports.FirstOrDefault(d => d.Type == server.RelayType);
{ if (transport == null) continue;
ITransport transport = transports.FirstOrDefault(d => d.Type == c.RelayType);
if (transport == null) return null;
IPEndPoint server = NetworkHelper.GetEndPoint(c.Host, 3478); IPEndPoint serverEP = NetworkHelper.GetEndPoint(server.Host, 3478);
RelayTestResultInfo result = await transport.RelayTestAsync(new RelayTestInfo
return new TestInfo
{
Server = c,
Task = transport.RelayTestAsync(new RelayTestInfo
{
MachineId = fileConfig.Data.Client.Id,
SecretKey = c.SecretKey,
Server = server,
})
};
}
catch (Exception)
{ {
} MachineId = fileConfig.Data.Client.Id,
return null; SecretKey = server.SecretKey,
}); Server = serverEP,
});
try server.Delay = result.Delay;
{ server.Available = result.Available;
await Task.WhenAll(tasks.Where(c => c != null).Select(c => c.Task)).WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false);
foreach (var item in tasks.Where(c => c != null))
{
item.Server.Delay = item.Task.Result.Delay;
item.Server.Available = item.Task.Result.Available;
}
}
catch (Exception)
{
} }
} }
catch (Exception) catch (Exception)

View File

@@ -228,7 +228,7 @@ namespace linker.plugins.relay.transport
{ {
Socket socket = new Socket(relayTestInfo.Server.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); Socket socket = new Socket(relayTestInfo.Server.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
socket.KeepAlive(); socket.KeepAlive();
await socket.ConnectAsync(relayTestInfo.Server).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false); await socket.ConnectAsync(relayTestInfo.Server).WaitAsync(TimeSpan.FromMilliseconds(1000)).ConfigureAwait(false);
connection = await messengerResolver.BeginReceiveClient(socket); connection = await messengerResolver.BeginReceiveClient(socket);
@@ -249,7 +249,6 @@ namespace linker.plugins.relay.transport
} }
catch (Exception) catch (Exception)
{ {
} }
finally finally
{ {

View File

@@ -46,17 +46,16 @@ namespace linker.plugins.tunnel
/// <returns></returns> /// <returns></returns>
public TunnelListInfo Get(ApiControllerParamsInfo param) public TunnelListInfo Get(ApiControllerParamsInfo param)
{ {
uint hashCode = uint.Parse(param.Content); ulong hashCode = ulong.Parse(param.Content);
uint _hashCode = tunnelConfigTransfer.ConfigVersion; if (tunnelConfigTransfer.Version.Eq(hashCode,out ulong version))
if (_hashCode != hashCode)
{ {
return new TunnelListInfo return new TunnelListInfo
{ {
List = tunnelConfigTransfer.Config, List = tunnelConfigTransfer.Config,
HashCode = _hashCode HashCode = version
}; };
} }
return new TunnelListInfo { HashCode = _hashCode }; return new TunnelListInfo { HashCode = version };
} }
/// <summary> /// <summary>
/// 刷新隧道信息 /// 刷新隧道信息
@@ -145,7 +144,7 @@ namespace linker.plugins.tunnel
public sealed class TunnelListInfo public sealed class TunnelListInfo
{ {
public ConcurrentDictionary<string, TunnelTransportRouteLevelInfo> List { get; set; } public ConcurrentDictionary<string, TunnelTransportRouteLevelInfo> List { get; set; }
public uint HashCode { get; set; } public ulong HashCode { get; set; }
} }
} }

View File

@@ -24,11 +24,8 @@ namespace linker.plugins.tunnel
private readonly TransportTcpPortMap transportTcpPortMap; private readonly TransportTcpPortMap transportTcpPortMap;
private readonly TransportUdpPortMap transportUdpPortMap; private readonly TransportUdpPortMap transportUdpPortMap;
public VersionManager Version { get; } = new VersionManager();
private uint version = 0; public ConcurrentDictionary<string, TunnelTransportRouteLevelInfo> Config { get; } = new ConcurrentDictionary<string, TunnelTransportRouteLevelInfo>();
public uint ConfigVersion => version;
private ConcurrentDictionary<string, TunnelTransportRouteLevelInfo> configs = new ConcurrentDictionary<string, TunnelTransportRouteLevelInfo>();
public ConcurrentDictionary<string, TunnelTransportRouteLevelInfo> Config => configs;
public TunnelConfigTransfer(FileConfig config, RunningConfig running, ClientSignInState clientSignInState, MessengerSender messengerSender, RunningConfigTransfer runningConfigTransfer, ITunnelAdapter tunnelAdapter, TransportTcpPortMap transportTcpPortMap, TransportUdpPortMap transportUdpPortMap) public TunnelConfigTransfer(FileConfig config, RunningConfig running, ClientSignInState clientSignInState, MessengerSender messengerSender, RunningConfigTransfer runningConfigTransfer, ITunnelAdapter tunnelAdapter, TransportTcpPortMap transportTcpPortMap, TransportUdpPortMap transportUdpPortMap)
{ {
@@ -108,8 +105,8 @@ namespace linker.plugins.tunnel
/// <returns></returns> /// <returns></returns>
public TunnelTransportRouteLevelInfo OnRemoteRouteLevel(TunnelTransportRouteLevelInfo tunnelTransportFileConfigInfo) public TunnelTransportRouteLevelInfo OnRemoteRouteLevel(TunnelTransportRouteLevelInfo tunnelTransportFileConfigInfo)
{ {
configs.AddOrUpdate(tunnelTransportFileConfigInfo.MachineId, tunnelTransportFileConfigInfo, (a, b) => tunnelTransportFileConfigInfo); Config.AddOrUpdate(tunnelTransportFileConfigInfo.MachineId, tunnelTransportFileConfigInfo, (a, b) => tunnelTransportFileConfigInfo);
Interlocked.Increment(ref version); Version.Add();
return GetLocalRouteLevel(); return GetLocalRouteLevel();
} }
private void GetRemoteRouteLevel() private void GetRemoteRouteLevel()
@@ -128,11 +125,11 @@ namespace linker.plugins.tunnel
List<TunnelTransportRouteLevelInfo> list = MemoryPackSerializer.Deserialize<List<TunnelTransportRouteLevelInfo>>(result.Result.Data.Span); List<TunnelTransportRouteLevelInfo> list = MemoryPackSerializer.Deserialize<List<TunnelTransportRouteLevelInfo>>(result.Result.Data.Span);
foreach (var item in list) foreach (var item in list)
{ {
configs.AddOrUpdate(item.MachineId, item, (a, b) => item); Config.AddOrUpdate(item.MachineId, item, (a, b) => item);
} }
TunnelTransportRouteLevelInfo config = GetLocalRouteLevel(); TunnelTransportRouteLevelInfo config = GetLocalRouteLevel();
configs.AddOrUpdate(config.MachineId, config, (a, b) => config); Config.AddOrUpdate(config.MachineId, config, (a, b) => config);
Interlocked.Increment(ref version); Version.Add();
} }
}); });
} }
@@ -145,7 +142,7 @@ namespace linker.plugins.tunnel
RouteLevelPlus = running.Data.Tunnel.RouteLevelPlus, RouteLevelPlus = running.Data.Tunnel.RouteLevelPlus,
PortMapWan = running.Data.Tunnel.PortMapWan, PortMapWan = running.Data.Tunnel.PortMapWan,
PortMapLan = running.Data.Tunnel.PortMapLan, PortMapLan = running.Data.Tunnel.PortMapLan,
NeedReboot = reboot NeedReboot = false
}; };
} }
private void InitRouteLevel() private void InitRouteLevel()
@@ -157,7 +154,6 @@ namespace linker.plugins.tunnel
} }
bool reboot = false;
private void TestQuic() private void TestQuic()
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
@@ -172,7 +168,6 @@ namespace linker.plugins.tunnel
File.Move("msquic.dll", "msquic.dll.temp", true); File.Move("msquic.dll", "msquic.dll.temp", true);
File.Move("msquic-openssl.dll", "msquic.dll", true); File.Move("msquic-openssl.dll", "msquic.dll", true);
reboot = true;
Environment.Exit(1); Environment.Exit(1);
} }
} }

View File

@@ -46,17 +46,16 @@ namespace linker.plugins.tuntap
/// <returns></returns> /// <returns></returns>
public TuntabListInfo Get(ApiControllerParamsInfo param) public TuntabListInfo Get(ApiControllerParamsInfo param)
{ {
uint hashCode = uint.Parse(param.Content); ulong hashCode = ulong.Parse(param.Content);
uint _hashCode = tuntapTransfer.InfosVersion; if (tuntapTransfer.Version.Eq(hashCode, out ulong version))
if (_hashCode != hashCode)
{ {
return new TuntabListInfo return new TuntabListInfo
{ {
List = tuntapTransfer.Infos, List = tuntapTransfer.Infos,
HashCode = _hashCode HashCode = version
}; };
} }
return new TuntabListInfo { HashCode = _hashCode }; return new TuntabListInfo { HashCode = version };
} }
/// <summary> /// <summary>
/// 刷新网卡信息 /// 刷新网卡信息
@@ -147,7 +146,7 @@ namespace linker.plugins.tuntap
public sealed class TuntabListInfo public sealed class TuntabListInfo
{ {
public ConcurrentDictionary<string, TuntapInfo> List { get; set; } public ConcurrentDictionary<string, TuntapInfo> List { get; set; }
public uint HashCode { get; set; } public ulong HashCode { get; set; }
} }
} }
} }

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.tunnel.connection;
namespace linker.plugins.tuntap namespace linker.plugins.tuntap
{ {
@@ -25,16 +26,15 @@ namespace linker.plugins.tuntap
private readonly LinkerTunDeviceAdapter linkerTunDeviceAdapter; private readonly LinkerTunDeviceAdapter linkerTunDeviceAdapter;
private string deviceName = "linker"; private string deviceName = "linker";
private uint operating = 0;
private List<IPAddress> routeIps = new List<IPAddress>(); private List<IPAddress> routeIps = new List<IPAddress>();
private uint infosVersion = 0; public VersionManager Version { get; } = new VersionManager();
public uint InfosVersion => infosVersion;
private readonly ConcurrentDictionary<string, TuntapInfo> tuntapInfos = new ConcurrentDictionary<string, TuntapInfo>(); private readonly ConcurrentDictionary<string, TuntapInfo> tuntapInfos = new ConcurrentDictionary<string, TuntapInfo>();
public ConcurrentDictionary<string, TuntapInfo> Infos => tuntapInfos; public ConcurrentDictionary<string, TuntapInfo> Infos => tuntapInfos;
public TuntapStatus Status => operating == 1 ? TuntapStatus.Operating : (TuntapStatus)(byte)linkerTunDeviceAdapter.Status;
private OperatingManager operatingManager = new OperatingManager();
public TuntapStatus Status => operatingManager.Operating ? TuntapStatus.Operating : (TuntapStatus)(byte)linkerTunDeviceAdapter.Status;
public TuntapTransfer(MessengerSender messengerSender, ClientSignInState clientSignInState, LinkerTunDeviceAdapter linkerTunDeviceAdapter, FileConfig config, TuntapProxy tuntapProxy, RunningConfig runningConfig) public TuntapTransfer(MessengerSender messengerSender, ClientSignInState clientSignInState, LinkerTunDeviceAdapter linkerTunDeviceAdapter, FileConfig config, TuntapProxy tuntapProxy, RunningConfig runningConfig)
{ {
@@ -59,6 +59,7 @@ namespace linker.plugins.tuntap
NetworkHelper.GetRouteLevel(config.Data.Client.Server, out routeIps); NetworkHelper.GetRouteLevel(config.Data.Client.Server, out routeIps);
NotifyConfig(); NotifyConfig();
CheckTuntapStatusTask(); CheckTuntapStatusTask();
PingTask();
if (runningConfig.Data.Tuntap.Running) if (runningConfig.Data.Tuntap.Running)
{ {
Setup(); Setup();
@@ -71,7 +72,7 @@ namespace linker.plugins.tuntap
/// </summary> /// </summary>
public void Setup() public void Setup()
{ {
if (Interlocked.CompareExchange(ref operating, 1, 0) == 1) if (operatingManager.StartOperation() == false)
{ {
return; return;
} }
@@ -111,7 +112,7 @@ namespace linker.plugins.tuntap
} }
private void SetupAfter() private void SetupAfter()
{ {
Interlocked.Exchange(ref operating, 0); operatingManager.StopOperation();
NotifyConfig(); NotifyConfig();
} }
private void SetupSuccess() private void SetupSuccess()
@@ -127,7 +128,7 @@ namespace linker.plugins.tuntap
/// </summary> /// </summary>
public void Shutdown() public void Shutdown()
{ {
if (Interlocked.CompareExchange(ref operating, 1, 0) == 1) if (operatingManager.StartOperation() == false)
{ {
return; return;
} }
@@ -155,7 +156,7 @@ namespace linker.plugins.tuntap
} }
private void ShutdownAfter() private void ShutdownAfter()
{ {
Interlocked.Exchange(ref operating, 0); operatingManager.StopOperation();
NotifyConfig(); NotifyConfig();
} }
private void ShutdownSuccess() private void ShutdownSuccess()
@@ -188,6 +189,8 @@ namespace linker.plugins.tuntap
runningConfig.Data.Tuntap.Masks = info.Masks; runningConfig.Data.Tuntap.Masks = info.Masks;
runningConfig.Data.Tuntap.PrefixLength = info.PrefixLength; runningConfig.Data.Tuntap.PrefixLength = info.PrefixLength;
runningConfig.Data.Tuntap.Gateway = info.Gateway; runningConfig.Data.Tuntap.Gateway = info.Gateway;
runningConfig.Data.Tuntap.ShowDelay = info.ShowDelay;
runningConfig.Data.Tuntap.AutoConnect = info.AutoConnect;
runningConfig.Data.Tuntap.Upgrade = info.Upgrade; runningConfig.Data.Tuntap.Upgrade = info.Upgrade;
runningConfig.Data.Tuntap.Forwards = info.Forwards; runningConfig.Data.Tuntap.Forwards = info.Forwards;
runningConfig.Data.Update(); runningConfig.Data.Update();
@@ -214,7 +217,7 @@ namespace linker.plugins.tuntap
{ {
DelRoute(); DelRoute();
tuntapInfos.AddOrUpdate(info.MachineId, info, (a, b) => info); tuntapInfos.AddOrUpdate(info.MachineId, info, (a, b) => info);
Interlocked.Increment(ref infosVersion); Version.Add();
AddRoute(); AddRoute();
}); });
@@ -240,7 +243,7 @@ namespace linker.plugins.tuntap
} }
AddRoute(); AddRoute();
} }
Interlocked.Increment(ref infosVersion); Version.Add();
}); });
} }
/// <summary> /// <summary>
@@ -260,9 +263,12 @@ namespace linker.plugins.tuntap
Error = linkerTunDeviceAdapter.Error, Error = linkerTunDeviceAdapter.Error,
Error1 = linkerTunDeviceAdapter.Error1, Error1 = linkerTunDeviceAdapter.Error1,
SystemInfo = $"{System.Runtime.InteropServices.RuntimeInformation.OSDescription} {(string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SNLTTY_LINKER_IS_DOCKER")) == false ? "Docker" : "")}", SystemInfo = $"{System.Runtime.InteropServices.RuntimeInformation.OSDescription} {(string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SNLTTY_LINKER_IS_DOCKER")) == false ? "Docker" : "")}",
Gateway = runningConfig.Data.Tuntap.Gateway,
Upgrade = runningConfig.Data.Tuntap.Upgrade,
Forwards = runningConfig.Data.Tuntap.Forwards, Forwards = runningConfig.Data.Tuntap.Forwards,
Switch = (runningConfig.Data.Tuntap.Gateway ? TuntapSwitch.Gateway : 0)
| (runningConfig.Data.Tuntap.Upgrade ? TuntapSwitch.Upgrade : 0)
| (runningConfig.Data.Tuntap.ShowDelay ? TuntapSwitch.ShowDelay : 0)
| (runningConfig.Data.Tuntap.AutoConnect ? TuntapSwitch.AutoConnect : 0)
}; };
if (runningConfig.Data.Tuntap.Masks.Length != runningConfig.Data.Tuntap.LanIPs.Length) if (runningConfig.Data.Tuntap.Masks.Length != runningConfig.Data.Tuntap.LanIPs.Length)
{ {
@@ -404,7 +410,7 @@ namespace linker.plugins.tuntap
await Task.Delay(15000).ConfigureAwait(false); await Task.Delay(15000).ConfigureAwait(false);
try try
{ {
if (runningConfig.Data.Tuntap.Running && OperatingSystem.IsWindows() && operating == 0) if (runningConfig.Data.Tuntap.Running && OperatingSystem.IsWindows() && operatingManager.Operating == false)
{ {
await CheckInterface().ConfigureAwait(false); await CheckInterface().ConfigureAwait(false);
} }
@@ -419,18 +425,64 @@ namespace linker.plugins.tuntap
{ {
NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(c => c.Name == deviceName); NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(c => c.Name == deviceName);
if (networkInterface == null || networkInterface.OperationalStatus != OperationalStatus.Up && operating == 0) if (networkInterface == null || networkInterface.OperationalStatus != OperationalStatus.Up && operatingManager.Operating == false)
{ {
LoggerHelper.Instance.Error($"tuntap inerface {deviceName} is {networkInterface?.OperationalStatus ?? OperationalStatus.Unknown}, restarting"); LoggerHelper.Instance.Error($"tuntap inerface {deviceName} is {networkInterface?.OperationalStatus ?? OperationalStatus.Unknown}, restarting");
Shutdown(); Shutdown();
await Task.Delay(5000).ConfigureAwait(false); await Task.Delay(5000).ConfigureAwait(false);
networkInterface = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(c => c.Name == deviceName); networkInterface = NetworkInterface.GetAllNetworkInterfaces().FirstOrDefault(c => c.Name == deviceName);
if (networkInterface == null || networkInterface.OperationalStatus != OperationalStatus.Up && operating == 0) if (networkInterface == null || networkInterface.OperationalStatus != OperationalStatus.Up && operatingManager.Operating == false)
{ {
Setup(); Setup();
} }
} }
} }
private void PingTask()
{
Task.Run(async () =>
{
while (true)
{
if (Status == TuntapStatus.Running && runningConfig.Data.Tuntap.ShowDelay)
{
var items = tuntapInfos.Values.Where(c => c.IP != null && c.IP.Equals(IPAddress.Any) == false);
if (runningConfig.Data.Tuntap.AutoConnect == false)
{
var connections = tuntapProxy.GetConnections();
items = items.Where(c => (connections.TryGetValue(c.MachineId, out ITunnelConnection connection) && connection.Connected) || c.MachineId == config.Data.Client.Id);
}
var tasks = items.Select(c =>
{
Ping ping = new Ping();
return new PingTaskInfo
{
TuntapInfo = c,
Ping = ping,
Task = ping.SendPingAsync(c.IP, 1000)
};
});
await Task.WhenAll(tasks.Select(c => c.Task));
foreach (var item in tasks.Where(c => c.Task.Result != null))
{
item.TuntapInfo.Delay = item.Task.Result.Status == IPStatus.Success ? (int)item.Task.Result.RoundtripTime : -1;
item.Ping.Dispose();
}
Version.Add();
}
await Task.Delay(3000);
}
});
}
public sealed class PingTaskInfo
{
public TuntapInfo TuntapInfo { get; set; }
public Ping Ping { get; set; }
public Task<PingReply> Task { get; set; }
}
} }
} }

View File

@@ -4,15 +4,18 @@ using System.Net;
namespace linker.plugins.tuntap.config namespace linker.plugins.tuntap.config
{ {
public sealed class TuntapConfigInfo [MemoryPackable]
public sealed partial class TuntapConfigInfo
{ {
/// <summary> /// <summary>
/// 网卡IP /// 网卡IP
/// </summary> /// </summary>
[MemoryPackAllowSerialize]
public IPAddress IP { get; set; } = IPAddress.Any; public IPAddress IP { get; set; } = IPAddress.Any;
/// <summary> /// <summary>
/// 局域网IP列表 /// 局域网IP列表
/// </summary> /// </summary>
[MemoryPackAllowSerialize]
public IPAddress[] LanIPs { get; set; } = Array.Empty<IPAddress>(); public IPAddress[] LanIPs { get; set; } = Array.Empty<IPAddress>();
/// <summary> /// <summary>
/// 局域网掩码列表与IP列表一一对应 /// 局域网掩码列表与IP列表一一对应
@@ -33,9 +36,19 @@ namespace linker.plugins.tuntap.config
/// </summary> /// </summary>
public bool Gateway { get; set; } public bool Gateway { get; set; }
/// <summary> /// <summary>
/// 显示延迟
/// </summary>
public bool ShowDelay { get; set; }
/// <summary>
/// 自动连接
/// </summary>
public bool AutoConnect { get; set; }
/// <summary>
/// 使用高级功能 /// 使用高级功能
/// </summary> /// </summary>
public bool Upgrade { get; set; } public bool Upgrade { get; set; }
/// <summary> /// <summary>
/// 端口转发列表 /// 端口转发列表
/// </summary> /// </summary>
@@ -103,6 +116,12 @@ namespace linker.plugins.tuntap.config
[MemoryPackAllowSerialize] [MemoryPackAllowSerialize]
public IPAddress IP { get; set; } public IPAddress IP { get; set; }
/// <summary>
/// 前缀长度
/// </summary>
public byte PrefixLength { get; set; } = 24;
/// <summary> /// <summary>
/// 局域网IP /// 局域网IP
/// </summary> /// </summary>
@@ -115,11 +134,6 @@ namespace linker.plugins.tuntap.config
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>
@@ -132,18 +146,120 @@ namespace linker.plugins.tuntap.config
/// 系统信息 /// 系统信息
/// </summary> /// </summary>
public string SystemInfo { get; set; } public string SystemInfo { get; set; }
/// <summary>
/// 是否网关
/// </summary>
public bool Gateway { get; set; }
/// <summary>
/// 使用高级功能
/// </summary>
public bool Upgrade { get; set; }
/// <summary> /// <summary>
/// 端口转发列表 /// 端口转发列表
/// </summary> /// </summary>
public List<TuntapForwardInfo> Forwards { get; set; } = new List<TuntapForwardInfo>(); public List<TuntapForwardInfo> Forwards { get; set; } = new List<TuntapForwardInfo>();
/// <summary>
/// 延迟ms
/// </summary>
public int Delay { get; set; } = -1;
/// <summary>
/// 开关多个bool集合
/// </summary>
public TuntapSwitch Switch { get; set; }
/// <summary>
/// 是否网关
/// </summary>
[MemoryPackIgnore]
public bool Gateway
{
get
{
return (Switch & TuntapSwitch.Gateway) == TuntapSwitch.Gateway;
}
set
{
if (value)
{
Switch |= TuntapSwitch.Gateway;
}
else
{
Switch &= ~TuntapSwitch.Gateway;
}
}
}
/// <summary>
/// 显示延迟
/// </summary>
[MemoryPackIgnore]
public bool ShowDelay
{
get
{
return (Switch & TuntapSwitch.ShowDelay) == TuntapSwitch.ShowDelay;
}
set
{
if (value)
{
Switch |= TuntapSwitch.ShowDelay;
}
else
{
Switch &= ~TuntapSwitch.ShowDelay;
}
}
}
/// <summary>
/// 自动连接
/// </summary>
[MemoryPackIgnore]
public bool AutoConnect
{
get
{
return (Switch & TuntapSwitch.AutoConnect) == TuntapSwitch.AutoConnect;
}
set
{
if (value)
{
Switch |= TuntapSwitch.AutoConnect;
}
else
{
Switch &= ~TuntapSwitch.AutoConnect;
}
}
}
/// <summary>
/// 使用高级功能
/// </summary>
[MemoryPackIgnore]
public bool Upgrade
{
get
{
return (Switch & TuntapSwitch.Upgrade) == TuntapSwitch.Upgrade;
}
set
{
if (value)
{
Switch |= TuntapSwitch.Upgrade;
}
else
{
Switch &= ~TuntapSwitch.Upgrade;
}
}
}
}
[Flags]
public enum TuntapSwitch
{
Gateway = 1,
ShowDelay = 2,
Upgrade = 4,
AutoConnect = 8,
} }

View File

@@ -9,8 +9,6 @@ using System.Collections.Concurrent;
using linker.plugins.tuntap.config; using linker.plugins.tuntap.config;
using linker.tun; using linker.tun;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Net.Sockets;
using System.Net;
namespace linker.plugins.tuntap.proxy namespace linker.plugins.tuntap.proxy
{ {
@@ -27,8 +25,7 @@ namespace linker.plugins.tuntap.proxy
private readonly ConcurrentDictionary<string, ITunnelConnection> connections = new ConcurrentDictionary<string, ITunnelConnection>(); private readonly ConcurrentDictionary<string, ITunnelConnection> connections = new ConcurrentDictionary<string, ITunnelConnection>();
private readonly ConcurrentDictionary<uint, ITunnelConnection> ipConnections = new ConcurrentDictionary<uint, ITunnelConnection>(); private readonly ConcurrentDictionary<uint, ITunnelConnection> ipConnections = new ConcurrentDictionary<uint, ITunnelConnection>();
SemaphoreSlim slimGlobal = new SemaphoreSlim(1); private readonly OperatingMultipleManager operatingMultipleManager = new OperatingMultipleManager();
private readonly ConcurrentDictionary<string, SemaphoreSlim> dicLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
public TuntapProxy(TunnelTransfer tunnelTransfer, RelayTransfer relayTransfer, RunningConfig runningConfig, FileConfig config, LinkerTunDeviceAdapter linkerTunDeviceAdapter) public TuntapProxy(TunnelTransfer tunnelTransfer, RelayTransfer relayTransfer, RunningConfig runningConfig, FileConfig config, LinkerTunDeviceAdapter linkerTunDeviceAdapter)
{ {
@@ -112,7 +109,6 @@ namespace linker.plugins.tuntap.proxy
} }
} }
/// <summary> /// <summary>
/// 设置IP等下有连接进来用IP匹配才能知道这个连接是要连谁 /// 设置IP等下有连接进来用IP匹配才能知道这个连接是要连谁
/// </summary> /// </summary>
@@ -179,23 +175,13 @@ namespace linker.plugins.tuntap.proxy
return connection; return connection;
} }
await slimGlobal.WaitAsync().ConfigureAwait(false); if(operatingMultipleManager.StartOperation(machineId) ==false)
if (dicLocks.TryGetValue(machineId, out SemaphoreSlim slim) == false)
{ {
slim = new SemaphoreSlim(1); return null;
dicLocks.TryAdd(machineId, slim);
} }
slimGlobal.Release();
await slim.WaitAsync().ConfigureAwait(false);
try try
{ {
if (connections.TryGetValue(machineId, out connection) && connection.Connected)
{
return connection;
}
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"tuntap tunnel to {machineId}"); if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"tuntap tunnel to {machineId}");
connection = await tunnelTransfer.ConnectAsync(machineId, "tuntap", TunnelProtocolType.None).ConfigureAwait(false); connection = await tunnelTransfer.ConnectAsync(machineId, "tuntap", TunnelProtocolType.None).ConfigureAwait(false);
@@ -210,7 +196,6 @@ namespace linker.plugins.tuntap.proxy
connection = await relayTransfer.ConnectAsync(config.Data.Client.Id, machineId, "tuntap").ConfigureAwait(false); connection = await relayTransfer.ConnectAsync(config.Data.Client.Id, machineId, "tuntap").ConfigureAwait(false);
if (connection != null) if (connection != null)
{ {
//tunnelTransfer.StartBackground(machineId, "tuntap");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"tuntap relay success,{connection.ToString()}"); if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"tuntap relay success,{connection.ToString()}");
} }
} }
@@ -224,7 +209,7 @@ namespace linker.plugins.tuntap.proxy
} }
finally finally
{ {
slim.Release(); operatingMultipleManager.StopOperation(machineId);
} }
return connection; return connection;
} }