From 0e741e46d3cba8dbab76e349cf5acd4e1cab9c07 Mon Sep 17 00:00:00 2001 From: snltty <1069410172@qq.com> Date: Sat, 27 Jul 2024 00:35:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=9F=BA=E4=BA=8E=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E6=98=A0=E5=B0=84=E7=9A=84P2P=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- linker.doc.web/docs/1、首页.md | 8 - linker.doc.web/docs/2、首次运行/2.1、安装.md | 6 +- .../docs/3、通信功能/3.3、虚拟网卡.md | 14 +- linker.tests/MemoryPackIPEndPointSerialize.cs | 467 +++++++++++++++++- linker.tunnel/TunnelTransfer.cs | 9 +- linker.tunnel/adapter/ITunnelAdapter.cs | 10 +- linker.tunnel/proxy/TunnelProxyTcp.cs | 8 - linker.tunnel/transport/ITunnelTransport.cs | 19 +- linker.tunnel/transport/TransportTcpNutssb.cs | 2 +- linker.tunnel/transport/TransportTcpP2PNAT.cs | 4 +- .../transport/TransportTcpPortMap.cs | 346 +++++++++++++ .../wanport/TunnelWanPortTransfer.cs | 4 +- linker.web/src/components/status/Index.vue | 2 +- linker.web/src/views/devices/TunnelEdit.vue | 27 +- linker/plugins/tunnel/TunnelAdapter.cs | 4 +- linker/plugins/tunnel/TunnelApiController.cs | 3 - linker/plugins/tunnel/TunnelConfigTransfer.cs | 21 +- linker/plugins/tunnel/TunnelStartup.cs | 5 +- linker/plugins/tunnel/config/Config.cs | 39 +- .../tunnel/messenger/TunnelMessenger.cs | 13 +- 20 files changed, 953 insertions(+), 58 deletions(-) create mode 100644 linker.tunnel/transport/TransportTcpPortMap.cs diff --git a/linker.doc.web/docs/1、首页.md b/linker.doc.web/docs/1、首页.md index 235ec936..bd213681 100644 --- a/linker.doc.web/docs/1、首页.md +++ b/linker.doc.web/docs/1、首页.md @@ -16,14 +16,6 @@ sidebar_position: 1 你可以加入QQ群:1121552990 -## 2、一些免费中继服务器 - -请各位善待 - -1. free、linker-hw-222200.snltty.com:1802、snltty -2. free、linker-hw-223400.snltty.com:1802、snltty -3. free、linker-hw-2451000.snltty.com:1802、snltty - ## 2、感谢支持 米多贝克 \ No newline at end of file diff --git a/linker.doc.web/docs/2、首次运行/2.1、安装.md b/linker.doc.web/docs/2、首次运行/2.1、安装.md index 5d0c830a..d69216bb 100644 --- a/linker.doc.web/docs/2、首次运行/2.1、安装.md +++ b/linker.doc.web/docs/2、首次运行/2.1、安装.md @@ -56,9 +56,13 @@ systemctl enable linker 5. debian `snltty/linker-debian-x64`、`snltty/linker-debian-arm64`,压缩约`70MB`、未压缩约`177MB` 6. alpine `snltty/linker-musl-x64`、`snltty/linker-musl-arm64`,压缩约`19MB`、未压缩约`43MB` -#### 特别说明 +:::tip[特别说明] + 1. 在容器中监听一个端口,宿主机无法访问,所以你需要把端口映射一下,比如 `-p 18000-18010:18000-18010`,预留这些端口,这样就可以使用这些端口做转发 2. 在docker容器中创建虚拟网卡,是无法直接通过虚拟网卡访问宿主机的,你需要把端口映射一下,或者直接使用`host`网络模式 + +::: + #### 客户端 ``` docker run -it -d --name linker \ diff --git a/linker.doc.web/docs/3、通信功能/3.3、虚拟网卡.md b/linker.doc.web/docs/3、通信功能/3.3、虚拟网卡.md index 31513a90..e3f3a126 100644 --- a/linker.doc.web/docs/3、通信功能/3.3、虚拟网卡.md +++ b/linker.doc.web/docs/3、通信功能/3.3、虚拟网卡.md @@ -18,8 +18,18 @@ sidebar_position: 3 ![Docusaurus Plushie](./img/tuntap.png) -1. 为目标设备设置一个IP地址,例如 **192.168.54.2** -2. 填写目标设备的局域网IP。比如目标设备局域网IP为**192.168.56.55**,局域网IP是可选的,且各个设备之间,局域网IP段不可重复,比如,不能两个设备的局域网IP 都是 **192.168.56.x/24** +1. 为目标设备设置一个IP地址,例如 **192.168.54.2**,每个客户端之间,填写相同网段的**网卡IP**,且掩码是**/24**的 +2. `局域网IP` 是选填的,可以不填 + +:::tip[局域网IP特别说明] + +1. 假设你 A 的内网IP是 **192.168.1.2**,B 的内网IP是 **192.168.2.3**,这样AB之间是无法相互连接的 +2. 这时 A 可以在 **局域网IP**填写 **192.168.1.2/24**,B 填写 **192.168.2.3/24**,这样AB之间就可以使用对方的内网IP相互访问 +3. 值得一提的是,如果你选择填写**局域网IP**,那么每个客户端之间,是不能填写相同网段的**局域网IP**,会冲突 + +::: + +> ![Docusaurus Plushie](./img/tuntap1.png) diff --git a/linker.tests/MemoryPackIPEndPointSerialize.cs b/linker.tests/MemoryPackIPEndPointSerialize.cs index 55b3f2b3..b193db01 100644 --- a/linker.tests/MemoryPackIPEndPointSerialize.cs +++ b/linker.tests/MemoryPackIPEndPointSerialize.cs @@ -1,4 +1,6 @@ -using linker.serializes; + +using linker.config; +using linker.plugins.serializes; using linker.tunnel.connection; using linker.tunnel.transport; using MemoryPack; @@ -12,9 +14,470 @@ namespace linker.Tests [TestMethod] public void Serialize() { - + MemoryPackFormatterProvider.Register(new TunnelTransportWanPortInfoFormatter1()); + MemoryPackFormatterProvider.Register(new TunnelTransportWanPortInfoFormatter2()); + MemoryPackFormatterProvider.Register(new TunnelTransportInfoFormatter1()); + MemoryPackFormatterProvider.Register(new TunnelTransportInfoFormatter2()); + MemoryPackFormatterProvider.Register(new IPEndPointFormatter()); + MemoryPackFormatterProvider.Register(new IPAddressFormatter()); + + + TunnelTransportInfo2 tunnelTransportInfo2 = new TunnelTransportInfo2 { Local = new TunnelTransportWanPortInfo2 { MachineName = "local2" }, Remote = new TunnelTransportWanPortInfo2 { MachineName = "remote2" } }; + byte[] bytes = MemoryPackSerializer.Serialize(tunnelTransportInfo2); + TunnelTransportInfo1 tunnelTransportInfo1 = MemoryPackSerializer.Deserialize(bytes); + + Assert.AreEqual(tunnelTransportInfo2.Local.MachineName, tunnelTransportInfo1.Local.MachineName); } } + + public sealed partial class TunnelTransportWanPortInfo1 + { + /// + /// ҵı + /// + public IPEndPoint Local { get; set; } + /// + /// ҵ + /// + public IPEndPoint Remote { get; set; } + /// + /// ҵľIP + /// + public IPAddress[] LocalIps { get; set; } + + /// + /// ҵ㼶 + /// + public int RouteLevel { get; set; } + + /// + /// ҵid + /// + public string MachineId { get; set; } + /// + /// ҵ + /// + public string MachineName { get; set; } + } + public sealed partial class TunnelTransportWanPortInfo2 + { + /// + /// ҵı + /// + public IPEndPoint Local { get; set; } + /// + /// ҵ + /// + public IPEndPoint Remote { get; set; } + /// + /// ҵľIP + /// + public IPAddress[] LocalIps { get; set; } + + /// + /// ҵ㼶 + /// + public int RouteLevel { get; set; } + + /// + /// ҵid + /// + public string MachineId { get; set; } + /// + /// ҵ + /// + public string MachineName { get; set; } + + + public int PortMapLan { get; set; } + public int PortMapWan { get; set; } + } + + [MemoryPackable] + public readonly partial struct SerializableTunnelTransportWanPortInfo1 + { + [MemoryPackIgnore] + public readonly TunnelTransportWanPortInfo1 tunnelTransportWanPortInfo; + + [MemoryPackInclude, MemoryPackAllowSerialize] + IPEndPoint Local => tunnelTransportWanPortInfo.Local; + + [MemoryPackInclude, MemoryPackAllowSerialize] + IPEndPoint Remote => tunnelTransportWanPortInfo.Remote; + + [MemoryPackInclude, MemoryPackAllowSerialize] + IPAddress[] LocalIps => tunnelTransportWanPortInfo.LocalIps; + + [MemoryPackInclude] + int RouteLevel => tunnelTransportWanPortInfo.RouteLevel; + + [MemoryPackInclude] + string MachineId => tunnelTransportWanPortInfo.MachineId; + + [MemoryPackInclude] + string MachineName => tunnelTransportWanPortInfo.MachineName; + + [MemoryPackConstructor] + SerializableTunnelTransportWanPortInfo1(IPEndPoint local, IPEndPoint remote, IPAddress[] localIps, int routeLevel, string machineId, string machineName) + { + var tunnelTransportWanPortInfo = new TunnelTransportWanPortInfo1 { Local = local, Remote = remote, LocalIps = localIps, RouteLevel = routeLevel, MachineId = machineId, MachineName = machineName }; + this.tunnelTransportWanPortInfo = tunnelTransportWanPortInfo; + } + + public SerializableTunnelTransportWanPortInfo1(TunnelTransportWanPortInfo1 tunnelTransportWanPortInfo) + { + this.tunnelTransportWanPortInfo = tunnelTransportWanPortInfo; + } + } + public class TunnelTransportWanPortInfoFormatter1 : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref TunnelTransportWanPortInfo1 value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializableTunnelTransportWanPortInfo1(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref TunnelTransportWanPortInfo1 value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + var wrapped = reader.ReadPackable(); + value = wrapped.tunnelTransportWanPortInfo; + } + } + + + [MemoryPackable] + public readonly partial struct SerializableTunnelTransportWanPortInfo2 + { + [MemoryPackIgnore] + public readonly TunnelTransportWanPortInfo2 tunnelTransportWanPortInfo; + + [MemoryPackInclude, MemoryPackAllowSerialize] + IPEndPoint Local => tunnelTransportWanPortInfo.Local; + + [MemoryPackInclude, MemoryPackAllowSerialize] + IPEndPoint Remote => tunnelTransportWanPortInfo.Remote; + + [MemoryPackInclude, MemoryPackAllowSerialize] + IPAddress[] LocalIps => tunnelTransportWanPortInfo.LocalIps; + + [MemoryPackInclude] + int RouteLevel => tunnelTransportWanPortInfo.RouteLevel; + + [MemoryPackInclude] + string MachineId => tunnelTransportWanPortInfo.MachineId; + + [MemoryPackInclude] + string MachineName => tunnelTransportWanPortInfo.MachineName; + + [MemoryPackInclude] + int PortMapLan => tunnelTransportWanPortInfo.PortMapLan; + + [MemoryPackInclude] + int PortMapWan => tunnelTransportWanPortInfo.PortMapWan; + + [MemoryPackConstructor] + SerializableTunnelTransportWanPortInfo2(IPEndPoint local, IPEndPoint remote, IPAddress[] localIps, int routeLevel, string machineId, string machineName, int portMapLan, int portMapWan) + { + var tunnelTransportWanPortInfo = new TunnelTransportWanPortInfo2 + { + Local = local, + Remote = remote, + LocalIps = localIps, + RouteLevel = routeLevel, + MachineId = machineId, + MachineName = machineName, + PortMapLan = portMapLan, + PortMapWan = portMapWan + }; + this.tunnelTransportWanPortInfo = tunnelTransportWanPortInfo; + } + + public SerializableTunnelTransportWanPortInfo2(TunnelTransportWanPortInfo2 tunnelTransportWanPortInfo) + { + this.tunnelTransportWanPortInfo = tunnelTransportWanPortInfo; + } + } + public class TunnelTransportWanPortInfoFormatter2 : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref TunnelTransportWanPortInfo2 value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializableTunnelTransportWanPortInfo2(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref TunnelTransportWanPortInfo2 value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + var wrapped = reader.ReadPackable(); + value = wrapped.tunnelTransportWanPortInfo; + } + } + + + public sealed partial class TunnelTransportInfo1 + { + /// + /// ҵ + /// + public TunnelTransportWanPortInfo1 Local { get; set; } + /// + /// Է + /// + public TunnelTransportWanPortInfo1 Remote { get; set; } + + /// + /// + /// + public string TransactionId { get; set; } + /// + /// Э + /// + public TunnelProtocolType TransportType { get; set; } + /// + /// Э + /// + public string TransportName { get; set; } + /// + /// + /// + public TunnelDirection Direction { get; set; } + /// + /// Ҫ + /// + public bool SSL { get; set; } + /// + /// + /// + public byte BufferSize { get; set; } = 3; + /// + /// Ŀipб + /// + public List RemoteEndPoints { get; set; } + } + public sealed partial class TunnelTransportInfo2 + { + /// + /// ҵ + /// + public TunnelTransportWanPortInfo2 Local { get; set; } + /// + /// Է + /// + public TunnelTransportWanPortInfo2 Remote { get; set; } + + /// + /// + /// + public string TransactionId { get; set; } + /// + /// Э + /// + public TunnelProtocolType TransportType { get; set; } + /// + /// Э + /// + public string TransportName { get; set; } + /// + /// + /// + public TunnelDirection Direction { get; set; } + /// + /// Ҫ + /// + public bool SSL { get; set; } + /// + /// + /// + public byte BufferSize { get; set; } = 3; + /// + /// Ŀipб + /// + public List RemoteEndPoints { get; set; } + } + [MemoryPackable] + public readonly partial struct SerializableTunnelTransportInfo1 + { + [MemoryPackIgnore] + public readonly TunnelTransportInfo1 tunnelTransportInfo; + + + [MemoryPackInclude, MemoryPackAllowSerialize] + TunnelTransportWanPortInfo1 Local => tunnelTransportInfo.Local; + + [MemoryPackInclude, MemoryPackAllowSerialize] + TunnelTransportWanPortInfo1 Remote => tunnelTransportInfo.Remote; + + [MemoryPackInclude] + string TransactionId => tunnelTransportInfo.TransactionId; + + [MemoryPackInclude] + TunnelProtocolType TransportType => tunnelTransportInfo.TransportType; + + [MemoryPackInclude] + string TransportName => tunnelTransportInfo.TransportName; + + [MemoryPackInclude] + TunnelDirection Direction => tunnelTransportInfo.Direction; + + [MemoryPackInclude] + bool SSL => tunnelTransportInfo.SSL; + + [MemoryPackInclude] + byte BufferSize => tunnelTransportInfo.BufferSize; + + + [MemoryPackConstructor] + SerializableTunnelTransportInfo1(TunnelTransportWanPortInfo1 local, TunnelTransportWanPortInfo1 remote, string transactionId, TunnelProtocolType transportType, string transportName, TunnelDirection direction, bool ssl, byte bufferSize) + { + var tunnelTransportInfo = new TunnelTransportInfo1 + { + Local = local, + Remote = remote, + TransactionId = transactionId, + TransportName = transportName, + TransportType = transportType, + Direction = direction, + SSL = ssl, + BufferSize = bufferSize, + }; + this.tunnelTransportInfo = tunnelTransportInfo; + } + + public SerializableTunnelTransportInfo1(TunnelTransportInfo1 tunnelTransportInfo) + { + this.tunnelTransportInfo = tunnelTransportInfo; + } + } + public class TunnelTransportInfoFormatter1 : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref TunnelTransportInfo1 value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializableTunnelTransportInfo1(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref TunnelTransportInfo1 value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + var wrapped = reader.ReadPackable(); + value = wrapped.tunnelTransportInfo; + } + } + + [MemoryPackable] + public readonly partial struct SerializableTunnelTransportInfo2 + { + [MemoryPackIgnore] + public readonly TunnelTransportInfo2 tunnelTransportInfo; + + + [MemoryPackInclude, MemoryPackAllowSerialize] + TunnelTransportWanPortInfo2 Local => tunnelTransportInfo.Local; + + [MemoryPackInclude, MemoryPackAllowSerialize] + TunnelTransportWanPortInfo2 Remote => tunnelTransportInfo.Remote; + + [MemoryPackInclude] + string TransactionId => tunnelTransportInfo.TransactionId; + + [MemoryPackInclude] + TunnelProtocolType TransportType => tunnelTransportInfo.TransportType; + + [MemoryPackInclude] + string TransportName => tunnelTransportInfo.TransportName; + + [MemoryPackInclude] + TunnelDirection Direction => tunnelTransportInfo.Direction; + + [MemoryPackInclude] + bool SSL => tunnelTransportInfo.SSL; + + [MemoryPackInclude] + byte BufferSize => tunnelTransportInfo.BufferSize; + + + [MemoryPackConstructor] + SerializableTunnelTransportInfo2(TunnelTransportWanPortInfo2 local, TunnelTransportWanPortInfo2 remote, string transactionId, TunnelProtocolType transportType, string transportName, TunnelDirection direction, bool ssl, byte bufferSize) + { + var tunnelTransportInfo = new TunnelTransportInfo2 + { + Local = local, + Remote = remote, + TransactionId = transactionId, + TransportName = transportName, + TransportType = transportType, + Direction = direction, + SSL = ssl, + BufferSize = bufferSize, + }; + this.tunnelTransportInfo = tunnelTransportInfo; + } + + public SerializableTunnelTransportInfo2(TunnelTransportInfo2 tunnelTransportInfo) + { + this.tunnelTransportInfo = tunnelTransportInfo; + } + } + public class TunnelTransportInfoFormatter2 : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref TunnelTransportInfo2 value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializableTunnelTransportInfo2(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref TunnelTransportInfo2 value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + var wrapped = reader.ReadPackable(); + value = wrapped.tunnelTransportInfo; + } + } } \ No newline at end of file diff --git a/linker.tunnel/TunnelTransfer.cs b/linker.tunnel/TunnelTransfer.cs index 32a916d5..85ba27aa 100644 --- a/linker.tunnel/TunnelTransfer.cs +++ b/linker.tunnel/TunnelTransfer.cs @@ -17,6 +17,7 @@ namespace linker.tunnel private ITunnelAdapter tunnelAdapter; private ConcurrentDictionary connectingDic = new ConcurrentDictionary(); + private uint flowid = 1; private Dictionary>> OnConnected { get; } = new Dictionary>>(); public TunnelTransfer() @@ -181,7 +182,8 @@ namespace linker.tunnel TransportType = transport.ProtocolType, Local = localInfo.Result, Remote = remoteInfo.Result, - SSL = transportItem.SSL + SSL = transportItem.SSL, + FlowId = Interlocked.Increment(ref flowid) }; OnConnecting(tunnelTransportInfo); ParseRemoteEndPoint(tunnelTransportInfo); @@ -295,6 +297,7 @@ namespace linker.tunnel TunnelWanPortEndPoint ip = await compactTransfer.GetWanPortAsync(tunnelAdapter.LocalIP, info).ConfigureAwait(false); if (ip != null) { + PortMapInfo portMapInfo = tunnelAdapter.PortMap ?? new PortMapInfo { LanPort = 0, WanPort = 0 }; var config = tunnelAdapter.GetLocalConfig(); return new TunnelTransportWanPortInfo { @@ -302,7 +305,9 @@ namespace linker.tunnel Remote = ip.Remote, LocalIps = config.LocalIps, RouteLevel = config.RouteLevel, - MachineId = config.MachineId + MachineId = config.MachineId, + PortMapLan = portMapInfo.LanPort, + PortMapWan = portMapInfo.WanPort, }; } return null; diff --git a/linker.tunnel/adapter/ITunnelAdapter.cs b/linker.tunnel/adapter/ITunnelAdapter.cs index b50b043d..6b13ff78 100644 --- a/linker.tunnel/adapter/ITunnelAdapter.cs +++ b/linker.tunnel/adapter/ITunnelAdapter.cs @@ -12,6 +12,8 @@ namespace linker.tunnel.adapter /// public IPAddress LocalIP { get; } + public PortMapInfo PortMap { get; } + /// /// ssl加密证书 /// @@ -26,7 +28,7 @@ namespace linker.tunnel.adapter /// 保存外网端口协议列表 /// /// - public void SetTunnelWanPortProtocols(List protocols,bool updateVersion); + public void SetTunnelWanPortProtocols(List protocols, bool updateVersion); /// /// 获取打洞协议列表 @@ -87,6 +89,12 @@ namespace linker.tunnel.adapter public string MachineId { get; set; } } + public sealed class PortMapInfo + { + public int WanPort { get; set; } + public int LanPort { get; set; } + } + public sealed class TunnelWanPortProtocolInfo { /// diff --git a/linker.tunnel/proxy/TunnelProxyTcp.cs b/linker.tunnel/proxy/TunnelProxyTcp.cs index 5e8e1bc9..33ef57cf 100644 --- a/linker.tunnel/proxy/TunnelProxyTcp.cs +++ b/linker.tunnel/proxy/TunnelProxyTcp.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; -using System.Collections.Generic; namespace linker.tunnel.proxy { @@ -91,13 +90,6 @@ namespace linker.tunnel.proxy } private async Task BeginReceive(AsyncUserToken token) { - int length = await token.Socket.ReceiveAsync(token.Buffer.AsMemory(), SocketFlags.None).ConfigureAwait(false); - if (length == 0) - { - CloseClientSocket(token); - return; - } - token.Proxy.Data = token.Buffer.AsMemory(0, length); bool closeConnect = await ConnectTunnelConnection(token).ConfigureAwait(false); if (token.Connection != null) { diff --git a/linker.tunnel/transport/ITunnelTransport.cs b/linker.tunnel/transport/ITunnelTransport.cs index b70009fb..50bf5c4c 100644 --- a/linker.tunnel/transport/ITunnelTransport.cs +++ b/linker.tunnel/transport/ITunnelTransport.cs @@ -25,7 +25,7 @@ namespace linker.tunnel.transport /// /// 是否反向打洞 /// - public bool Reverse { get; } + public bool Reverse { get; } /// /// 是否允许修改反向打洞配置 /// @@ -37,7 +37,7 @@ namespace linker.tunnel.transport /// /// 是否允许修改ssl配置 /// - public bool DisableSSL { get; } + public bool DisableSSL { get; } /// /// 发送连接开始信息 @@ -111,6 +111,15 @@ namespace linker.tunnel.transport /// 我的名称 /// public string MachineName { get; set; } + + /// + /// 固定端口,外网 + /// + public int PortMapWan { get; set; } + /// + /// 固定端口,内网 + /// + public int PortMapLan { get; set; } } public sealed partial class TunnelTransportItemInfo @@ -200,6 +209,12 @@ namespace linker.tunnel.transport /// 缓冲区 /// public byte BufferSize { get; set; } = 3; + + /// + /// 流id + /// + public uint FlowId { get; set; } + /// /// 目标ip列表 /// diff --git a/linker.tunnel/transport/TransportTcpNutssb.cs b/linker.tunnel/transport/TransportTcpNutssb.cs index 269cc720..fa50e4eb 100644 --- a/linker.tunnel/transport/TransportTcpNutssb.cs +++ b/linker.tunnel/transport/TransportTcpNutssb.cs @@ -104,7 +104,7 @@ namespace linker.tunnel.transport if (tunnelTransportInfo.SSL && tunnelAdapter.Certificate == null) { LoggerHelper.Instance.Error($"{Name}->ssl Certificate not found"); - await OnSendConnectSuccess(tunnelTransportInfo).ConfigureAwait(false); + await OnSendConnectFail(tunnelTransportInfo).ConfigureAwait(false); return; } //正向连接,也就是它要连接我,那我就监听 diff --git a/linker.tunnel/transport/TransportTcpP2PNAT.cs b/linker.tunnel/transport/TransportTcpP2PNAT.cs index 92b63513..e8cfab88 100644 --- a/linker.tunnel/transport/TransportTcpP2PNAT.cs +++ b/linker.tunnel/transport/TransportTcpP2PNAT.cs @@ -65,7 +65,7 @@ namespace linker.tunnel.transport { return null; } - if(tunnelTransportInfo.Direction == TunnelDirection.Reverse) + if (tunnelTransportInfo.Direction == TunnelDirection.Reverse) { await Task.Delay(50).ConfigureAwait(false); } @@ -88,7 +88,7 @@ namespace linker.tunnel.transport if (tunnelTransportInfo.SSL && tunnelAdapter.Certificate == null) { LoggerHelper.Instance.Error($"{Name}->ssl Certificate not found"); - await OnSendConnectSuccess(tunnelTransportInfo).ConfigureAwait(false); + await OnSendConnectFail(tunnelTransportInfo).ConfigureAwait(false); return; } ITunnelConnection connection = await ConnectForward(tunnelTransportInfo, TunnelMode.Server).ConfigureAwait(false); diff --git a/linker.tunnel/transport/TransportTcpPortMap.cs b/linker.tunnel/transport/TransportTcpPortMap.cs new file mode 100644 index 00000000..9b488173 --- /dev/null +++ b/linker.tunnel/transport/TransportTcpPortMap.cs @@ -0,0 +1,346 @@ +using linker.tunnel.connection; +using linker.tunnel.wanport; +using System.Net.Sockets; +using System.Net; +using System.Text; +using linker.libs.extends; +using System.Collections.Concurrent; +using linker.libs; +using linker.tunnel.adapter; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Security.Authentication; + +namespace linker.tunnel.transport +{ + /// + /// 基于端口映射 + /// + public sealed class TransportTcpPortMap : ITunnelTransport + { + public string Name => "TcpPortMap"; + + public string Label => "Tcp端口映射"; + + public TunnelProtocolType ProtocolType => TunnelProtocolType.Tcp; + + public TunnelWanPortProtocolType AllowWanPortProtocolType => TunnelWanPortProtocolType.Tcp | TunnelWanPortProtocolType.Udp; + + public bool Reverse => true; + + public bool DisableReverse => false; + + public bool SSL => true; + + public bool DisableSSL => false; + + public Func> OnSendConnectBegin { get; set; } = async (info) => { return await Task.FromResult(false); }; + public Func OnSendConnectFail { get; set; } = async (info) => { await Task.CompletedTask; }; + public Func OnSendConnectSuccess { get; set; } = async (info) => { await Task.CompletedTask; }; + public Action OnConnected { get; set; } = (state) => { }; + + + private readonly ConcurrentDictionary> distDic = new ConcurrentDictionary>(); + private readonly ITunnelAdapter tunnelAdapter; + public TransportTcpPortMap(ITunnelAdapter tunnelAdapter) + { + this.tunnelAdapter = tunnelAdapter; + } + + Socket socket; + public async Task Listen(int localPort) + { + if (socket != null && (socket.LocalEndPoint as IPEndPoint).Port == localPort) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + LoggerHelper.Instance.Warning($"{Name} {socket.LocalEndPoint} already exists"); + } + return; + } + + try + { + socket?.SafeClose(); + if (localPort == 0) return; + + IPAddress localIP = IPAddress.Any; + + socket = new Socket(localIP.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + socket.IPv6Only(localIP.AddressFamily, false); + socket.ReuseBind(new IPEndPoint(localIP, localPort)); + socket.Listen(int.MaxValue); + + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + LoggerHelper.Instance.Debug($"{Name} listen {localPort}"); + } + + while (true) + { + try + { + Socket client = await socket.AcceptAsync(); + + _ = Task.Run(async () => + { + try + { + byte[] bytes = new byte[1024]; + int length = await client.ReceiveAsync(bytes.AsMemory()).AsTask().WaitAsync(TimeSpan.FromMilliseconds(3000)); + if (length > 0) + { + string key = bytes.AsMemory(0, length).GetString(); + LoggerHelper.Instance.Debug(key); + if (distDic.TryRemove(key, out TaskCompletionSource tcs)) + { + await client.SendAsync(bytes.AsMemory(0, length)); + tcs.SetResult(client); + return; + } + } + client.SafeClose(); + } + catch (Exception) + { + client.SafeClose(); + } + }); + } + catch (Exception) + { + break; + } + } + } + catch (Exception ex) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + LoggerHelper.Instance.Error(ex); + } + } + } + + public async Task ConnectAsync(TunnelTransportInfo tunnelTransportInfo) + { + if (tunnelTransportInfo.Direction == TunnelDirection.Forward) + { + if (tunnelTransportInfo.Remote.PortMapWan == 0) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + LoggerHelper.Instance.Error($"ConnectAsync Forward【{Name}】{tunnelTransportInfo.Remote.MachineName} port mapping not configured"); + return null; + } + //正向连接 + if (await OnSendConnectBegin(tunnelTransportInfo).ConfigureAwait(false) == false) + { + return null; + } + await Task.Delay(100).ConfigureAwait(false); + ITunnelConnection connection = await ConnectForward(tunnelTransportInfo).ConfigureAwait(false); + if (connection != null) + { + await OnSendConnectSuccess(tunnelTransportInfo).ConfigureAwait(false); + return connection; + } + } + else if (tunnelTransportInfo.Direction == TunnelDirection.Reverse) + { + if (tunnelTransportInfo.Local.PortMapWan == 0) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + LoggerHelper.Instance.Error($"ConnectAsync Reverse【{Name}】{tunnelTransportInfo.Local.MachineName} port mapping not configured"); + return null; + } + //反向连接 + TunnelTransportInfo tunnelTransportInfo1 = tunnelTransportInfo.ToJsonFormat().DeJson(); + //等待对方连接,如果连接成功,我会收到一个socket,并且创建一个连接对象,失败的话会超时,那就是null + var task = WaitConnect(tunnelTransportInfo1); + if (await OnSendConnectBegin(tunnelTransportInfo1).ConfigureAwait(false) == false) + { + return null; + } + ITunnelConnection connection = await task.ConfigureAwait(false); + if (connection != null) + { + await OnSendConnectSuccess(tunnelTransportInfo).ConfigureAwait(false); + return connection; + } + } + + + await OnSendConnectFail(tunnelTransportInfo).ConfigureAwait(false); + return null; + } + public async Task OnBegin(TunnelTransportInfo tunnelTransportInfo) + { + if (tunnelTransportInfo.SSL && tunnelAdapter.Certificate == null) + { + LoggerHelper.Instance.Error($"{Name}->ssl Certificate not found"); + await OnSendConnectFail(tunnelTransportInfo).ConfigureAwait(false); + return; + } + //正向连接,等他来连 + if (tunnelTransportInfo.Direction == TunnelDirection.Forward) + { + if (tunnelTransportInfo.Local.PortMapWan == 0) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + LoggerHelper.Instance.Error($"OnBegin WaitConnect 【{Name}】{tunnelTransportInfo.Local.MachineName} port mapping not configured"); + await OnSendConnectFail(tunnelTransportInfo).ConfigureAwait(false); + return; + } + _ = WaitConnect(tunnelTransportInfo).ContinueWith((result) => + { + OnConnected(result.Result); + }); + } + //我要连它,那就连接 + else + { + if (tunnelTransportInfo.Remote.PortMapWan == 0) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + LoggerHelper.Instance.Error($"OnBegin ConnectForward 【{Name}】{tunnelTransportInfo.Remote.MachineName} port mapping not configured"); + await OnSendConnectFail(tunnelTransportInfo).ConfigureAwait(false); + return; + } + + ITunnelConnection connection = await ConnectForward(tunnelTransportInfo).ConfigureAwait(false); + if (connection != null) + { + OnConnected(connection); + await OnSendConnectSuccess(tunnelTransportInfo).ConfigureAwait(false); + } + else + { + await OnSendConnectFail(tunnelTransportInfo).ConfigureAwait(false); + } + } + } + + public void OnFail(TunnelTransportInfo tunnelTransportInfo) + { + } + public void OnSuccess(TunnelTransportInfo tunnelTransportInfo) + { + } + + private async Task WaitConnect(TunnelTransportInfo tunnelTransportInfo) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + string key = $"{tunnelTransportInfo.Remote.MachineId}-{tunnelTransportInfo.FlowId}"; + distDic.TryAdd(key, tcs); + try + { + Socket socket = await tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(5000)); + + socket.KeepAlive(); + SslStream sslStream = null; + if (tunnelTransportInfo.SSL) + { + if (tunnelAdapter.Certificate == null) + { + LoggerHelper.Instance.Error($"{Name}-> ssl Certificate not found"); + socket.SafeClose(); + return null; + } + + sslStream = new SslStream(new NetworkStream(socket, false), false); + await sslStream.AuthenticateAsServerAsync(tunnelAdapter.Certificate, false, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13, false).ConfigureAwait(false); + } + + TunnelConnectionTcp result = new TunnelConnectionTcp + { + RemoteMachineId = tunnelTransportInfo.Remote.MachineId, + RemoteMachineName = tunnelTransportInfo.Remote.MachineName, + Direction = tunnelTransportInfo.Direction, + ProtocolType = TunnelProtocolType.Tcp, + Stream = sslStream, + Socket = socket, + Type = TunnelType.P2P, + Mode = TunnelMode.Server, + TransactionId = tunnelTransportInfo.TransactionId, + TransportName = tunnelTransportInfo.TransportName, + IPEndPoint = socket.RemoteEndPoint as IPEndPoint, + Label = string.Empty, + SSL = tunnelTransportInfo.SSL, + BufferSize = tunnelTransportInfo.BufferSize, + }; + return result; + } + catch (Exception) + { + } + finally + { + distDic.TryRemove(key, out _); + } + return null; + } + private async Task ConnectForward(TunnelTransportInfo tunnelTransportInfo) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + LoggerHelper.Instance.Warning($"{Name} connect to {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName} {string.Join("\r\n", tunnelTransportInfo.RemoteEndPoints.Select(c => c.ToString()))}"); + } + + IPEndPoint ep = new IPEndPoint(tunnelTransportInfo.Remote.Remote.Address, tunnelTransportInfo.Remote.PortMapWan); + Socket targetSocket = new(ep.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + try + { + targetSocket.KeepAlive(); + targetSocket.ReuseBind(new IPEndPoint(tunnelTransportInfo.Local.Local.Address, tunnelTransportInfo.Local.Local.Port)); + + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + LoggerHelper.Instance.Warning($"{Name} connect to {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName} {ep}"); + } + await targetSocket.ConnectAsync(ep).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false); + + await targetSocket.SendAsync($"{tunnelTransportInfo.Local.MachineId}-{tunnelTransportInfo.FlowId}".ToBytes()); + await targetSocket.ReceiveAsync(new byte[1024]).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false); ; + + //需要ssl + SslStream sslStream = null; + if (tunnelTransportInfo.SSL) + { + sslStream = new SslStream(new NetworkStream(targetSocket, false), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null); + await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions { EnabledSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13 }).ConfigureAwait(false); + } + + return new TunnelConnectionTcp + { + Stream = sslStream, + Socket = targetSocket, + IPEndPoint = targetSocket.RemoteEndPoint as IPEndPoint, + TransactionId = tunnelTransportInfo.TransactionId, + RemoteMachineId = tunnelTransportInfo.Remote.MachineId, + RemoteMachineName = tunnelTransportInfo.Remote.MachineName, + TransportName = Name, + Direction = tunnelTransportInfo.Direction, + ProtocolType = ProtocolType, + Type = TunnelType.P2P, + Mode = TunnelMode.Client, + Label = string.Empty, + SSL = tunnelTransportInfo.SSL, + BufferSize = tunnelTransportInfo.BufferSize, + }; + } + catch (Exception ex) + { + if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + LoggerHelper.Instance.Error($"{Name} connect {ep} fail {ex}"); + } + targetSocket.SafeClose(); + } + return null; + } + private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + return true; + } + } +} diff --git a/linker.tunnel/wanport/TunnelWanPortTransfer.cs b/linker.tunnel/wanport/TunnelWanPortTransfer.cs index a324628b..72a97921 100644 --- a/linker.tunnel/wanport/TunnelWanPortTransfer.cs +++ b/linker.tunnel/wanport/TunnelWanPortTransfer.cs @@ -1,6 +1,4 @@ using linker.libs; -using linker.libs.extends; -using System.Diagnostics; using System.Net; namespace linker.tunnel.wanport @@ -54,7 +52,7 @@ namespace linker.tunnel.wanport if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"get domain ip {info.Host}"); IPEndPoint server = NetworkHelper.GetEndPoint(info.Host, 3478); - if(server == null) return null; + if (server == null) return null; TunnelWanPortEndPoint wanPort = await tunnelWanPort.GetAsync(server).ConfigureAwait(false); if (wanPort != null) { diff --git a/linker.web/src/components/status/Index.vue b/linker.web/src/components/status/Index.vue index 1204f140..5d5a6e72 100644 --- a/linker.web/src/components/status/Index.vue +++ b/linker.web/src/components/status/Index.vue @@ -1,6 +1,6 @@