diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b520825b..130b9172 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: release_name: v2.0.0.${{ steps.date.outputs.today }} draft: false prerelease: false - body: "1. 一些累计更新,一些BUG修复\r\n2. #92 松开打洞loading限制,允许选择中继节点\r\n3. #89 windows下利用任务计划进行进程守护,定时检查服务\r\n4. 优化客户端管理ws并发\r\n5. 减少服务器推送,减轻主服务器压力\r\n6. 优化中继,针对无法连接自己的外网IP的情况" + body: "1. 一些累计更新,一些BUG修复\r\n2. #92 松开打洞loading限制,允许选择中继节点\r\n3. #89 windows下利用任务计划进行进程守护,定时检查服务\r\n4. 优化客户端管理ws并发\r\n5. 减少服务器推送,减轻主服务器压力\r\n6. 优化中继,针对无法连接自己的外网IP的情况\r\n7. 优化upnp,自己实现了一遍" - name: setup node.js uses: actions/setup-node@v2 with: diff --git a/.github/workflows/loongarch.yml b/.github/workflows/loongarch.yml index 1a8028ea..1a722af5 100644 --- a/.github/workflows/loongarch.yml +++ b/.github/workflows/loongarch.yml @@ -36,7 +36,8 @@ jobs: cat src/linker/linker.csproj && \ dotnet nuget locals all --clear && \ uname -m && \ - dotnet run src/linker + dotnet publish src/linker -c release -f net9.0 -o public/publish/loongarch64 -r linux-loongarch64 -p:PublishSingleFile=true --self-contained true \ + -p:TrimMode=partial -p:TieredPGO=true -p:DebugType=full -p:EventSourceSupport=false -p:DebugSymbols=true -p:EnableCompressionInSingleFile=true -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:HttpActivityPropagationSupport=false -p:InvariantGlobalization=true -p:MetadataUpdaterSupport=false -p:UseSystemResourceKeys=true -p:MetricsSupport=false -p:StackTraceSupport=false -p:XmlResolverIsNetworkingEnabledByDefault=false " diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 19c39d37..b503c3d7 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -52,6 +52,7 @@ jobs: dotnet build ./src/linker.tun -c release dotnet build ./src/linker.nat -c release dotnet build ./src/linker.tunnel -c release + dotnet build ./src/linker.upnp -c release - name: Pack run: | @@ -87,6 +88,7 @@ jobs: dotnet pack ./src/linker.tun -c release dotnet pack ./src/linker.nat -c release dotnet pack ./src/linker.tunnel -c release + dotnet pack ./src/linker.upnp -c release - name: Push run: | @@ -122,3 +124,4 @@ jobs: dotnet nuget push ./src/linker.tun/bin/release/linker.tun.2.0.0.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols dotnet nuget push ./src/linker.nat/bin/release/linker.nat.2.0.0.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols dotnet nuget push ./src/linker.tunnel/bin/release/linker.tunnel.2.0.0.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols + dotnet nuget push ./src/linker.upnp/bin/release/linker.upnp.2.0.0.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols diff --git a/linker.sln b/linker.sln index 644bc011..5334edac 100644 --- a/linker.sln +++ b/linker.sln @@ -73,6 +73,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.messenger.wlist", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "linker.messenger.node", "src\linker.messenger.node\linker.messenger.node.csproj", "{96BBC3AB-E248-6EE8-4969-4623C5D62E12}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "linker.upnp", "src\linker.upnp\linker.upnp.csproj", "{E360590A-36A3-4454-B8A9-48F35F908C35}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -509,6 +511,18 @@ Global {96BBC3AB-E248-6EE8-4969-4623C5D62E12}.Release|x64.Build.0 = Release|Any CPU {96BBC3AB-E248-6EE8-4969-4623C5D62E12}.Release|x86.ActiveCfg = Release|Any CPU {96BBC3AB-E248-6EE8-4969-4623C5D62E12}.Release|x86.Build.0 = Release|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Debug|x64.ActiveCfg = Debug|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Debug|x64.Build.0 = Debug|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Debug|x86.ActiveCfg = Debug|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Debug|x86.Build.0 = Debug|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Release|Any CPU.Build.0 = Release|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Release|x64.ActiveCfg = Release|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Release|x64.Build.0 = Release|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Release|x86.ActiveCfg = Release|Any CPU + {E360590A-36A3-4454-B8A9-48F35F908C35}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/shells/version.txt b/shells/version.txt index 0b993f19..739423fd 100644 --- a/shells/version.txt +++ b/shells/version.txt @@ -1,8 +1,9 @@ v2.0.0 -2026-02-28 15:31:05 +2026-03-04 17:32:56 1. 一些累计更新,一些BUG修复 2. #92 松开打洞loading限制,允许选择中继节点 3. #89 windows下利用任务计划进行进程守护,定时检查服务 4. 优化客户端管理ws并发 5. 减少服务器推送,减轻主服务器压力 -6. 优化中继,针对无法连接自己的外网IP的情况 \ No newline at end of file +6. 优化中继,针对无法连接自己的外网IP的情况 +7. 优化upnp,自己实现了一遍 \ No newline at end of file diff --git a/shells/ymls/nuget.yml b/shells/ymls/nuget.yml index 206a8efd..8ef7ff42 100644 --- a/shells/ymls/nuget.yml +++ b/shells/ymls/nuget.yml @@ -52,6 +52,7 @@ jobs: dotnet build ./src/linker.tun -c release dotnet build ./src/linker.nat -c release dotnet build ./src/linker.tunnel -c release + dotnet build ./src/linker.upnp -c release - name: Pack run: | @@ -87,6 +88,7 @@ jobs: dotnet pack ./src/linker.tun -c release dotnet pack ./src/linker.nat -c release dotnet pack ./src/linker.tunnel -c release + dotnet pack ./src/linker.upnp -c release - name: Push run: | @@ -122,3 +124,4 @@ jobs: dotnet nuget push ./src/linker.tun/bin/release/linker.tun.{{version}}.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols dotnet nuget push ./src/linker.nat/bin/release/linker.nat.{{version}}.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols dotnet nuget push ./src/linker.tunnel/bin/release/linker.tunnel.{{version}}.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols + dotnet nuget push ./src/linker.upnp/bin/release/linker.upnp.{{version}}.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_KEY }} --no-symbols diff --git a/src/linker.libs/NetworkHelper.cs b/src/linker.libs/NetworkHelper.cs index ed8bdf5e..972409f3 100644 --- a/src/linker.libs/NetworkHelper.cs +++ b/src/linker.libs/NetworkHelper.cs @@ -42,6 +42,27 @@ namespace linker.libs public static partial class NetworkHelper { + private static readonly HashSet privateNetworks = new HashSet + { + // IPv4 私有网络 + System.Net.IPNetwork.Parse("127.0.0.0/8"), // 回环 + System.Net.IPNetwork.Parse("10.0.0.0/8"), // 私有A类 + System.Net.IPNetwork.Parse("172.16.0.0/12"), // 私有B类 + System.Net.IPNetwork.Parse("192.168.0.0/16"), // 私有C类 + System.Net.IPNetwork.Parse("169.254.0.0/16"), // 链路本地 + System.Net.IPNetwork.Parse("100.64.0.0/10"), // CGNAT (可选) + + // IPv6 私有网络 + System.Net.IPNetwork.Parse("fc00::/7"), // ULA + System.Net.IPNetwork.Parse("fe80::/10"), // 链路本地 + System.Net.IPNetwork.Parse("::1/128"), // 回环 + }; + public static bool IsPrivateIP(IPAddress ip) + { + return privateNetworks.Any(network => network.Contains(ip)); + } + + public static IPEndPoint TransEndpointFamily(IPEndPoint ep) { if (ep.Address.AddressFamily == AddressFamily.InterNetworkV6 && ep.Address.IsIPv4MappedToIPv6) @@ -172,7 +193,7 @@ namespace linker.libs for (ushort i = 1; i < lines.Length; i++) { string ip = regex.Match(lines[i]).Groups[1].Value; - if (starts.Any(c => ip.ToString().StartsWith(c))) + if (IsPrivateIP(IPAddress.Parse(ip))) { result.Add(IPAddress.Parse(ip)); } @@ -200,7 +221,7 @@ namespace linker.libs continue; } - if (starts.Any(c => reply.Address.ToString().StartsWith(c))) + if (IsPrivateIP(reply.Address)) { result.Add(reply.Address); } diff --git a/src/linker.libs/web/WebServer.cs b/src/linker.libs/web/WebServer.cs index 984a19d3..5b402c0e 100644 --- a/src/linker.libs/web/WebServer.cs +++ b/src/linker.libs/web/WebServer.cs @@ -321,7 +321,7 @@ namespace linker.libs.web } catch (Exception ex) { - LoggerHelper.Instance.Error($"{model.Path} -> {ex.Message}"); + LoggerHelper.Instance.Error($"{model.Path} -> {ex}"); return new ApiControllerResponseInfo { Content = ex.Message, diff --git a/src/linker.messenger.flow/Entry.cs b/src/linker.messenger.flow/Entry.cs index 2f9b1011..89363549 100644 --- a/src/linker.messenger.flow/Entry.cs +++ b/src/linker.messenger.flow/Entry.cs @@ -5,8 +5,8 @@ using linker.messenger.flow.webapi; using linker.messenger.forward.proxy; using linker.messenger.relay.server; using linker.messenger.socks5; -using linker.messenger.tunnel; -using linker.messenger.tuntap; +using linker.messenger.tunnel.server; +using linker.messenger.tuntap.client; using linker.plugins.sforward.proxy; using Microsoft.Extensions.DependencyInjection; namespace linker.messenger.flow diff --git a/src/linker.messenger.flow/FlowExternal.cs b/src/linker.messenger.flow/FlowExternal.cs index deeece1f..0dd39a0e 100644 --- a/src/linker.messenger.flow/FlowExternal.cs +++ b/src/linker.messenger.flow/FlowExternal.cs @@ -1,5 +1,5 @@ using linker.libs; -using linker.messenger.tunnel; +using linker.messenger.tunnel.server; namespace linker.messenger.flow { public sealed class FlowExternal : IFlow diff --git a/src/linker.messenger.flow/FlowResolver.cs b/src/linker.messenger.flow/FlowResolver.cs index 253fd5d0..c7924a7d 100644 --- a/src/linker.messenger.flow/FlowResolver.cs +++ b/src/linker.messenger.flow/FlowResolver.cs @@ -2,7 +2,7 @@ using linker.libs.extends; using linker.libs.timer; using linker.messenger.signin; -using linker.messenger.tunnel; +using linker.messenger.tunnel.client; using System.Buffers; using System.Collections.Concurrent; using System.Net; diff --git a/src/linker.messenger.flow/FlowTunnel.cs b/src/linker.messenger.flow/FlowTunnel.cs index 0608d86f..e145c842 100644 --- a/src/linker.messenger.flow/FlowTunnel.cs +++ b/src/linker.messenger.flow/FlowTunnel.cs @@ -3,8 +3,8 @@ using linker.libs.extends; using linker.messenger.channel; using linker.messenger.pcp; using linker.messenger.signin; -using linker.messenger.tuntap; using linker.messenger.tuntap.cidr; +using linker.messenger.tuntap.client; using linker.tunnel; using linker.tunnel.connection; using System.Collections.Concurrent; diff --git a/src/linker.messenger.listen/CountryTransfer.cs b/src/linker.messenger.listen/CountryTransfer.cs index 0187d40d..9641ff2c 100644 --- a/src/linker.messenger.listen/CountryTransfer.cs +++ b/src/linker.messenger.listen/CountryTransfer.cs @@ -11,6 +11,7 @@ namespace linker.messenger.listen { public sealed class CountryTransfer { + private string[] countryCodes = ["--","AF", "AX", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "CV", "KH", "CM", "CA", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CW", "CY", "CZ", @@ -64,15 +65,10 @@ namespace linker.messenger.listen Span address = stackalloc byte[4]; ip.TryWriteBytes(address, out _); - if (address[0] == 127 || address[0] == 10 - || (address[0] == 172 && address[1] >= 16 && address[1] <= 31) - || (address[0] == 192 && address[1] == 168) - || (address[0] == 169 && address[1] == 254) - ) + if (NetworkHelper.IsPrivateIP(ip) ) { return true; } - uint value = BinaryPrimitives.ReadUInt32BigEndian(address); if (ipCaches.TryGetValue(value, out bool result)) { diff --git a/src/linker.messenger.serializer.memorypack/Entry.cs b/src/linker.messenger.serializer.memorypack/Entry.cs index 56ae1e5d..9711762e 100644 --- a/src/linker.messenger.serializer.memorypack/Entry.cs +++ b/src/linker.messenger.serializer.memorypack/Entry.cs @@ -43,6 +43,7 @@ namespace linker.messenger.serializer.memorypack MemoryPackFormatterProvider.Register(new TunnelInterfaceInfoFormatter()); MemoryPackFormatterProvider.Register(new TunnelNetInfoFormatter()); MemoryPackFormatterProvider.Register(new TunnelTransportItemSetInfoFormatter()); + MemoryPackFormatterProvider.Register(new PortMappingInfoFormatter()); MemoryPackFormatterProvider.Register(new DecenterSyncInfoFormatter()); diff --git a/src/linker.messenger.serializer.memorypack/TunnelSerializer.cs b/src/linker.messenger.serializer.memorypack/TunnelSerializer.cs index 7da79bf2..b172e11d 100644 --- a/src/linker.messenger.serializer.memorypack/TunnelSerializer.cs +++ b/src/linker.messenger.serializer.memorypack/TunnelSerializer.cs @@ -1,9 +1,11 @@ -using linker.tunnel.connection; +using linker.messenger.tunnel; +using linker.tunnel.connection; using linker.tunnel.transport; using linker.tunnel.wanport; -using System.Net; +using linker.upnp; using MemoryPack; -using linker.messenger.tunnel; +using System.Net; +using System.Net.Sockets; namespace linker.messenger.serializer.memorypack { @@ -744,4 +746,92 @@ namespace linker.messenger.serializer.memorypack value = wrapped.info; } } + + + + + [MemoryPackable] + public readonly partial struct SerializablePortMappingInfo + { + [MemoryPackIgnore] + public readonly PortMappingInfo info; + + [MemoryPackInclude, MemoryPackAllowSerialize] + IPAddress ClientIp => info.ClientIp; + [MemoryPackInclude] + int PublicPort => info.PublicPort; + [MemoryPackInclude] + int PrivatePort => info.PrivatePort; + [MemoryPackInclude] + ProtocolType ProtocolType => info.ProtocolType; + [MemoryPackInclude] + bool Enabled => info.Enabled; + [MemoryPackInclude] + string Description => info.Description; + [MemoryPackInclude] + int LeaseDuration => info.LeaseDuration; + [MemoryPackInclude] + DeviceType DeviceType => info.DeviceType; + + [MemoryPackInclude] + bool Deletable => info.Deletable; + + [MemoryPackConstructor] + SerializablePortMappingInfo(IPAddress clientIp, int publicPort, int privatePort, ProtocolType protocolType, bool enabled, string description, int leaseDuration, DeviceType deviceType, bool deletable) + { + var info = new PortMappingInfo + { + ClientIp = clientIp, + PublicPort = publicPort, + PrivatePort = privatePort, + ProtocolType = protocolType, + Enabled = enabled, + Description = description, + LeaseDuration = leaseDuration, + DeviceType = deviceType, + Deletable = deletable + }; + this.info = info; + } + + public SerializablePortMappingInfo(PortMappingInfo info) + { + this.info = info; + } + } + public class PortMappingInfoFormatter : MemoryPackFormatter + { + public override void Serialize(ref MemoryPackWriter writer, scoped ref PortMappingInfo value) + { + if (value == null) + { + writer.WriteNullObjectHeader(); + return; + } + + writer.WritePackable(new SerializablePortMappingInfo(value)); + } + + public override void Deserialize(ref MemoryPackReader reader, scoped ref PortMappingInfo value) + { + if (reader.PeekIsNull()) + { + reader.Advance(1); // skip null block + value = null; + return; + } + + value = new PortMappingInfo(); + reader.TryReadObjectHeader(out byte count); + value.ClientIp = reader.ReadValue(); + value.PublicPort = reader.ReadValue(); + value.PrivatePort = reader.ReadValue(); + value.ProtocolType = reader.ReadValue(); + value.Enabled = reader.ReadValue(); + value.Description = reader.ReadValue(); + value.LeaseDuration = reader.ReadValue(); + value.DeviceType = reader.ReadValue(); + value.Deletable = reader.ReadValue(); + } + } } diff --git a/src/linker.messenger.store.file/Entry.cs b/src/linker.messenger.store.file/Entry.cs index 96e144fd..8e94da55 100644 --- a/src/linker.messenger.store.file/Entry.cs +++ b/src/linker.messenger.store.file/Entry.cs @@ -37,8 +37,8 @@ using linker.messenger.store.file.updater; using linker.messenger.store.file.wakeup; using linker.messenger.store.file.wlist; using linker.messenger.sync; -using linker.messenger.tunnel; -using linker.messenger.tuntap; +using linker.messenger.tunnel.client; +using linker.messenger.tuntap.client; using linker.messenger.tuntap.lease; using linker.messenger.updater; using linker.messenger.wakeup; diff --git a/src/linker.messenger.store.file/tunnel/TunnelClientStore.cs b/src/linker.messenger.store.file/tunnel/TunnelClientStore.cs index 35a1501c..7d4ce25d 100644 --- a/src/linker.messenger.store.file/tunnel/TunnelClientStore.cs +++ b/src/linker.messenger.store.file/tunnel/TunnelClientStore.cs @@ -1,5 +1,6 @@ using linker.libs; using linker.messenger.tunnel; +using linker.messenger.tunnel.client; using linker.tunnel.transport; using System.Net; diff --git a/src/linker.messenger.store.file/tuntap/TuntapClientStore.cs b/src/linker.messenger.store.file/tuntap/TuntapClientStore.cs index 7ecae5ad..87024bdc 100644 --- a/src/linker.messenger.store.file/tuntap/TuntapClientStore.cs +++ b/src/linker.messenger.store.file/tuntap/TuntapClientStore.cs @@ -1,4 +1,5 @@ using linker.messenger.tuntap; +using linker.messenger.tuntap.client; namespace linker.messenger.store.file.tuntap { diff --git a/src/linker.messenger.tunnel/Entry.cs b/src/linker.messenger.tunnel/Entry.cs index a89e53b4..e6ddbbc0 100644 --- a/src/linker.messenger.tunnel/Entry.cs +++ b/src/linker.messenger.tunnel/Entry.cs @@ -5,6 +5,8 @@ using Microsoft.Extensions.DependencyInjection; using linker.messenger.signin.args; using linker.messenger.sync; using linker.libs.web; +using linker.messenger.tunnel.server; +using linker.messenger.tunnel.client; namespace linker.messenger.tunnel { public static class Entry diff --git a/src/linker.messenger.tunnel/TunnelMessenger.cs b/src/linker.messenger.tunnel/TunnelMessenger.cs index 4f5d7348..11885e71 100644 --- a/src/linker.messenger.tunnel/TunnelMessenger.cs +++ b/src/linker.messenger.tunnel/TunnelMessenger.cs @@ -2,6 +2,10 @@ using linker.tunnel.transport; using linker.libs; using linker.messenger.signin; +using linker.messenger.tunnel.client; +using linker.libs.extends; +using linker.upnp; +using System.Net.Sockets; namespace linker.messenger.tunnel { @@ -123,6 +127,32 @@ namespace linker.messenger.tunnel await tunnelMessengerAdapter.SetTunnelTransports(info.MachineId, info.Data); connection.Write(Helper.TrueArray); } + + [MessengerId((ushort)TunnelMessengerIds.UpnpGet)] + public async Task UpnpGet(IConnection connection) + { + uint requestid = connection.ReceiveRequestWrap.RequestId; + connection.Write(serializer.Serialize(tunnelNetworkTransfer.GetMapping())); + } + + [MessengerId((ushort)TunnelMessengerIds.UpnpGetLocal)] + public async Task UpnpGetLocal(IConnection connection) + { + connection.Write(serializer.Serialize(tunnelNetworkTransfer.GetMappingLocal())); + } + + [MessengerId((ushort)TunnelMessengerIds.UpnpAdd)] + public async Task UpnpAdd(IConnection connection) + { + KeyValuePair info = serializer.Deserialize>(connection.ReceiveRequestWrap.Payload.Span); + _ = tunnelNetworkTransfer.AddMapping(info.Value).ConfigureAwait(false); + } + [MessengerId((ushort)TunnelMessengerIds.UpnpDel)] + public async Task UpnpDel(IConnection connection) + { + KeyValuePair> info = serializer.Deserialize>>(connection.ReceiveRequestWrap.Payload.Span); + _ = tunnelNetworkTransfer.DelMapping(info.Value.Key, info.Value.Value).ConfigureAwait(false); + } } /// @@ -321,5 +351,103 @@ namespace linker.messenger.tunnel }); } } + + + [MessengerId((ushort)TunnelMessengerIds.UpnpGetForward)] + public void UpnpGetForward(IConnection connection) + { + string machineid = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, machineid, out SignCacheInfo from, out SignCacheInfo to)) + { + uint requestid = connection.ReceiveRequestWrap.RequestId; + _ = messengerSender.SendReply(new MessageRequestWrap + { + Connection = to.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpGet, + }).ContinueWith(async (result) => + { + if (result.Result.Code == MessageResponeCodes.OK && result.Result.Data.Length > 0) + { + await messengerSender.ReplyOnly(new MessageResponseWrap + { + Connection = connection, + Payload = result.Result.Data, + RequestId = requestid, + }, (ushort)TunnelMessengerIds.UpnpGetForward).ConfigureAwait(false); + } + }); + } + } + [MessengerId((ushort)TunnelMessengerIds.UpnpGetLocalForward)] + public void UpnpGetLocalForward(IConnection connection) + { + string machineid = serializer.Deserialize(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, machineid, out SignCacheInfo from, out SignCacheInfo to)) + { + uint requestid = connection.ReceiveRequestWrap.RequestId; + _ = messengerSender.SendReply(new MessageRequestWrap + { + Connection = to.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpGetLocal, + }).ContinueWith(async (result) => + { + if (result.Result.Code == MessageResponeCodes.OK && result.Result.Data.Length > 0) + { + await messengerSender.ReplyOnly(new MessageResponseWrap + { + Connection = connection, + Payload = result.Result.Data, + RequestId = requestid, + }, (ushort)TunnelMessengerIds.UpnpGetLocalForward).ConfigureAwait(false); + } + }); + } + } + [MessengerId((ushort)TunnelMessengerIds.UpnpAddForward)] + public void UpnpAddForward(IConnection connection) + { + KeyValuePair info = serializer.Deserialize>(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, info.Key, out SignCacheInfo from, out SignCacheInfo to)) + { + uint requestid = connection.ReceiveRequestWrap.RequestId; + _ = messengerSender.SendReply(new MessageRequestWrap + { + Connection = to.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpAdd, + Payload = connection.ReceiveRequestWrap.Payload + }).ContinueWith(async (result) => + { + await messengerSender.ReplyOnly(new MessageResponseWrap + { + Connection = connection, + Payload = Helper.TrueArray, + RequestId = requestid, + }, (ushort)TunnelMessengerIds.UpnpAddForward).ConfigureAwait(false); + }); + } + } + [MessengerId((ushort)TunnelMessengerIds.UpnpDelForward)] + public void UpnpDelForward(IConnection connection) + { + KeyValuePair> info = serializer.Deserialize>>(connection.ReceiveRequestWrap.Payload.Span); + if (signCaching.TryGet(connection.Id, info.Key, out SignCacheInfo from, out SignCacheInfo to)) + { + uint requestid = connection.ReceiveRequestWrap.RequestId; + _ = messengerSender.SendReply(new MessageRequestWrap + { + Connection = to.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpDel, + Payload = connection.ReceiveRequestWrap.Payload + }).ContinueWith(async (result) => + { + await messengerSender.ReplyOnly(new MessageResponseWrap + { + Connection = connection, + Payload = Helper.TrueArray, + RequestId = requestid, + }, (ushort)TunnelMessengerIds.UpnpDelForward).ConfigureAwait(false); + }); + } + } } } diff --git a/src/linker.messenger.tunnel/TunnelMessengerIds.cs b/src/linker.messenger.tunnel/TunnelMessengerIds.cs index 9ed9e52c..37272986 100644 --- a/src/linker.messenger.tunnel/TunnelMessengerIds.cs +++ b/src/linker.messenger.tunnel/TunnelMessengerIds.cs @@ -27,6 +27,16 @@ TransportSet = 2017, TransportSetForward = 2018, + UpnpGet = 2019, + UpnpGetForward = 2020, + UpnpGetLocal = 2021, + UpnpGetLocalForward = 2022, + + UpnpAdd = 2023, + UpnpAddForward = 2024, + UpnpDel = 2025, + UpnpDelForward = 2026, + None = 2099 } } diff --git a/src/linker.messenger.tunnel/ITunnelClientExcludeIP.cs b/src/linker.messenger.tunnel/client/ITunnelClientExcludeIP.cs similarity index 82% rename from src/linker.messenger.tunnel/ITunnelClientExcludeIP.cs rename to src/linker.messenger.tunnel/client/ITunnelClientExcludeIP.cs index 6d13d9c8..8cb4124d 100644 --- a/src/linker.messenger.tunnel/ITunnelClientExcludeIP.cs +++ b/src/linker.messenger.tunnel/client/ITunnelClientExcludeIP.cs @@ -1,6 +1,6 @@ using linker.tunnel.transport; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { /// /// 打洞排除IP diff --git a/src/linker.messenger.tunnel/SignInArgsNet.cs b/src/linker.messenger.tunnel/client/SignInArgsNet.cs similarity index 96% rename from src/linker.messenger.tunnel/SignInArgsNet.cs rename to src/linker.messenger.tunnel/client/SignInArgsNet.cs index d2193c7f..4057eee7 100644 --- a/src/linker.messenger.tunnel/SignInArgsNet.cs +++ b/src/linker.messenger.tunnel/client/SignInArgsNet.cs @@ -1,7 +1,7 @@ using linker.messenger.signin.args; using linker.libs.extends; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { public sealed class SignInArgsNet : ISignInArgsClient { diff --git a/src/linker.messenger.tunnel/TunnelApiController.cs b/src/linker.messenger.tunnel/client/TunnelApiController.cs similarity index 69% rename from src/linker.messenger.tunnel/TunnelApiController.cs rename to src/linker.messenger.tunnel/client/TunnelApiController.cs index 0b96ff3c..4384045f 100644 --- a/src/linker.messenger.tunnel/TunnelApiController.cs +++ b/src/linker.messenger.tunnel/client/TunnelApiController.cs @@ -1,14 +1,16 @@ -using linker.tunnel.transport; +using linker.libs; using linker.libs.extends; -using System.Collections.Concurrent; -using linker.messenger.signin; -using linker.libs; -using linker.messenger.api; -using linker.tunnel.connection; -using linker.tunnel; using linker.libs.web; +using linker.messenger.api; +using linker.messenger.signin; +using linker.tunnel; +using linker.tunnel.connection; +using linker.tunnel.transport; +using linker.upnp; +using System.Collections.Concurrent; +using System.Net.Sockets; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { /// /// 管理接口 @@ -38,6 +40,80 @@ namespace linker.messenger.tunnel this.tunnelNetworkTransfer = tunnelNetworkTransfer; this.tunnelTransfer = tunnelTransfer; this.tunnelMessengerAdapter = tunnelMessengerAdapter; + + } + + public async Task> GetMapping(ApiControllerParamsInfo param) + { + if (param.Content == signInClientStore.Id || string.IsNullOrWhiteSpace(param.Content)) + { + return tunnelNetworkTransfer.GetMapping(); + } + + MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap + { + Connection = signInClientState.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpGetForward, + Payload = serializer.Serialize(param.Content) + }).ConfigureAwait(false); + if (resp.Code == MessageResponeCodes.OK && resp.Data.Length > 0) + { + return serializer.Deserialize>(resp.Data.Span); + } + return []; + } + public async Task> GetMappingLocal(ApiControllerParamsInfo param) + { + if (param.Content == signInClientStore.Id || string.IsNullOrWhiteSpace(param.Content)) + { + return tunnelNetworkTransfer.GetMappingLocal(); + } + + MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap + { + Connection = signInClientState.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpGetLocalForward, + Payload = serializer.Serialize(param.Content) + }).ConfigureAwait(false); + if (resp.Code == MessageResponeCodes.OK && resp.Data.Length > 0) + { + return serializer.Deserialize>(resp.Data.Span); + } + return []; + } + public async Task AddMapping(ApiControllerParamsInfo param) + { + KeyValueInfo info = param.Content.DeJson>(); + if (info.Key == signInClientStore.Id || string.IsNullOrWhiteSpace(info.Key)) + { + await tunnelNetworkTransfer.AddMapping(info.Value).ConfigureAwait(false); + return true; + } + + MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap + { + Connection = signInClientState.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpAddForward, + Payload = serializer.Serialize(new KeyValuePair(info.Key, info.Value)) + }).ConfigureAwait(false); + return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray); + } + public async Task DelMapping(ApiControllerParamsInfo param) + { + KeyValueInfo> info = param.Content.DeJson>>(); + if (info.Key == signInClientStore.Id || string.IsNullOrWhiteSpace(info.Key)) + { + await tunnelNetworkTransfer.DelMapping(info.Value.Key, info.Value.Value).ConfigureAwait(false); + return true; + } + + MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap + { + Connection = signInClientState.Connection, + MessengerId = (ushort)TunnelMessengerIds.UpnpDelForward, + Payload = serializer.Serialize(new KeyValuePair>(info.Key, new KeyValuePair(info.Value.Key, info.Value.Value))) + }).ConfigureAwait(false); + return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray); } /// @@ -176,7 +252,6 @@ namespace linker.messenger.tunnel return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray); } - public async Task GetNetwork(ApiControllerParamsInfo param) { if (param.Content == signInClientStore.Id) diff --git a/src/linker.messenger.tunnel/TunnelClientExcludeIPTransfer.cs b/src/linker.messenger.tunnel/client/TunnelClientExcludeIPTransfer.cs similarity index 95% rename from src/linker.messenger.tunnel/TunnelClientExcludeIPTransfer.cs rename to src/linker.messenger.tunnel/client/TunnelClientExcludeIPTransfer.cs index f5ba5ea9..d7202b6f 100644 --- a/src/linker.messenger.tunnel/TunnelClientExcludeIPTransfer.cs +++ b/src/linker.messenger.tunnel/client/TunnelClientExcludeIPTransfer.cs @@ -1,6 +1,6 @@ using linker.tunnel.transport; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { /// /// 打洞排除IP diff --git a/src/linker.messenger.tunnel/TunnelClientMessengerAdapter.cs b/src/linker.messenger.tunnel/client/TunnelClientMessengerAdapter.cs similarity index 99% rename from src/linker.messenger.tunnel/TunnelClientMessengerAdapter.cs rename to src/linker.messenger.tunnel/client/TunnelClientMessengerAdapter.cs index 98236053..7183d11b 100644 --- a/src/linker.messenger.tunnel/TunnelClientMessengerAdapter.cs +++ b/src/linker.messenger.tunnel/client/TunnelClientMessengerAdapter.cs @@ -6,7 +6,7 @@ using System.Security.Cryptography.X509Certificates; using linker.messenger.signin; using linker.messenger.decenter; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { /// /// 打洞信标适配存储 diff --git a/src/linker.messenger.tunnel/TunnelDecenter.cs b/src/linker.messenger.tunnel/client/TunnelDecenter.cs similarity index 98% rename from src/linker.messenger.tunnel/TunnelDecenter.cs rename to src/linker.messenger.tunnel/client/TunnelDecenter.cs index 76da8817..eda58d75 100644 --- a/src/linker.messenger.tunnel/TunnelDecenter.cs +++ b/src/linker.messenger.tunnel/client/TunnelDecenter.cs @@ -2,7 +2,7 @@ using linker.messenger.decenter; using linker.messenger.signin; using System.Collections.Concurrent; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { public sealed class TunnelDecenter : IDecenter { diff --git a/src/linker.messenger.tunnel/TunnelExRoute.cs b/src/linker.messenger.tunnel/client/TunnelExRoute.cs similarity index 88% rename from src/linker.messenger.tunnel/TunnelExRoute.cs rename to src/linker.messenger.tunnel/client/TunnelExRoute.cs index 66a011b7..25a219b4 100644 --- a/src/linker.messenger.tunnel/TunnelExRoute.cs +++ b/src/linker.messenger.tunnel/client/TunnelExRoute.cs @@ -1,8 +1,7 @@ - -using linker.messenger.exroute; +using linker.messenger.exroute; using System.Net; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { public sealed class TunnelExRoute : IExRoute { diff --git a/src/linker.messenger.tunnel/TunnelNetworkTransfer.cs b/src/linker.messenger.tunnel/client/TunnelNetworkTransfer.cs similarity index 87% rename from src/linker.messenger.tunnel/TunnelNetworkTransfer.cs rename to src/linker.messenger.tunnel/client/TunnelNetworkTransfer.cs index 55980e59..026c7336 100644 --- a/src/linker.messenger.tunnel/TunnelNetworkTransfer.cs +++ b/src/linker.messenger.tunnel/client/TunnelNetworkTransfer.cs @@ -1,10 +1,12 @@ using linker.libs; using linker.libs.extends; using linker.libs.timer; +using linker.messenger.decenter; using linker.messenger.signin; using linker.messenger.tunnel.stun.client; using linker.messenger.tunnel.stun.enums; using linker.tunnel; +using linker.upnp; using System.Net; using System.Net.NetworkInformation; using System.Net.Quic; @@ -12,7 +14,7 @@ using System.Net.Sockets; using System.Text.Json.Nodes; using System.Text.RegularExpressions; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { public sealed class TunnelNetworkTransfer { @@ -24,7 +26,7 @@ namespace linker.messenger.tunnel public Action OnChange { get; set; } = () => { }; - public TunnelNetworkTransfer(ISignInClientStore signInClientStore, SignInClientState signInClientState, ITunnelClientStore tunnelClientStore, IMessengerSender messengerSender, ISerializer serializer, TunnelTransfer tunnelTransfer) + public TunnelNetworkTransfer(ISignInClientStore signInClientStore, SignInClientState signInClientState, ITunnelClientStore tunnelClientStore, IMessengerSender messengerSender, ISerializer serializer, TunnelTransfer tunnelTransfer, CounterDecenter counterDecenter) { this.signInClientStore = signInClientStore; this.signInClientState = signInClientState; @@ -32,7 +34,6 @@ namespace linker.messenger.tunnel this.messengerSender = messengerSender; this.serializer = serializer; - signInClientState.OnSignInSuccessBefore += async () => { RefreshRouteLevel(); tunnelTransfer.Refresh(); await Task.CompletedTask; }; TestQuic(); @@ -40,7 +41,35 @@ namespace linker.messenger.tunnel RefreshRouteLevel(); GetNet(); + + PortMappingUtility.StartDiscovery(); + PortMappingUtility.OnChange += () => + { + counterDecenter.SetValue("upnp-d", PortMappingUtility.DeviceCount); + counterDecenter.SetValue("upnp-r", PortMappingUtility.MappingCount); + counterDecenter.SetValue("upnp-l", PortMappingUtility.LocalMappingCount); + counterDecenter.SetValue("upnp-w", PortMappingUtility.WanCount); + }; } + + public List GetMapping() + { + return PortMappingUtility.Get(); + } + public List GetMappingLocal() + { + return PortMappingUtility.GetLocal(); + } + public async Task AddMapping(PortMappingInfo mapping) + { + await PortMappingUtility.Add(mapping).ConfigureAwait(false); + } + public async Task DelMapping(int publicPort, ProtocolType ProtocolType) + { + await PortMappingUtility.Delete(publicPort, ProtocolType).ConfigureAwait(false); + } + + /// /// 刷新网关等级数据 /// @@ -66,7 +95,7 @@ namespace linker.messenger.tunnel try { using HttpClient httpClient = new HttpClient(); - string str = await httpClient.GetStringAsync($"http://ip-api.com/json",cts.Token).ConfigureAwait(false); + string str = await httpClient.GetStringAsync($"http://ip-api.com/json", cts.Token).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(str) == false) { @@ -91,7 +120,7 @@ namespace linker.messenger.tunnel try { using HttpClient httpClient = new HttpClient(); - string str = await httpClient.GetStringAsync($"https://api.myip.la/en?json",cts.Token).ConfigureAwait(false); + string str = await httpClient.GetStringAsync($"https://api.myip.la/en?json", cts.Token).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(str) == false) { @@ -140,7 +169,6 @@ namespace linker.messenger.tunnel } return false; } - private void GetNet() { TimerHelper.Async(async () => @@ -182,6 +210,7 @@ namespace linker.messenger.tunnel Routes = tunnelClientStore.Network.RouteIPs, }; } + private static byte[] ipv6LocalBytes = new byte[] { 254, 128, 0, 0, 0, 0, 0, 0 }; private TunnelInterfaceInfo[] GetInterfaces() { diff --git a/src/linker.messenger.tunnel/TunnelSync.cs b/src/linker.messenger.tunnel/client/TunnelSync.cs similarity index 74% rename from src/linker.messenger.tunnel/TunnelSync.cs rename to src/linker.messenger.tunnel/client/TunnelSync.cs index 245d068f..55880d32 100644 --- a/src/linker.messenger.tunnel/TunnelSync.cs +++ b/src/linker.messenger.tunnel/client/TunnelSync.cs @@ -3,18 +3,16 @@ using linker.messenger.sync; using linker.tunnel; using linker.tunnel.transport; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.client { public sealed class TunnelSyncTransports : ISync { public string Name => "TunnelTransports"; - private readonly ITunnelClientStore tunnelClientStore; private readonly ISerializer serializer; private readonly ITunnelMessengerAdapter tunnelMessengerAdapter; - public TunnelSyncTransports(ITunnelClientStore tunnelClientStore, ISerializer serializer, ITunnelMessengerAdapter tunnelMessengerAdapter) + public TunnelSyncTransports(ISerializer serializer, ITunnelMessengerAdapter tunnelMessengerAdapter) { - this.tunnelClientStore = tunnelClientStore; this.serializer = serializer; this.tunnelMessengerAdapter = tunnelMessengerAdapter; } diff --git a/src/linker.messenger.tunnel/TunnelServerExternalResolver.cs b/src/linker.messenger.tunnel/server/TunnelServerExternalResolver.cs similarity index 98% rename from src/linker.messenger.tunnel/TunnelServerExternalResolver.cs rename to src/linker.messenger.tunnel/server/TunnelServerExternalResolver.cs index a4f6932e..83ec95fc 100644 --- a/src/linker.messenger.tunnel/TunnelServerExternalResolver.cs +++ b/src/linker.messenger.tunnel/server/TunnelServerExternalResolver.cs @@ -5,7 +5,7 @@ using linker.libs.extends; using linker.libs; using System.Text; -namespace linker.messenger.tunnel +namespace linker.messenger.tunnel.server { /// /// 外网端口处理器 diff --git a/src/linker.messenger.tuntap/Entry.cs b/src/linker.messenger.tuntap/Entry.cs index 5d8a6cf7..29f94c1a 100644 --- a/src/linker.messenger.tuntap/Entry.cs +++ b/src/linker.messenger.tuntap/Entry.cs @@ -4,8 +4,9 @@ using linker.libs.web; using linker.messenger.decenter; using linker.messenger.exroute; using linker.messenger.signin; -using linker.messenger.tunnel; +using linker.messenger.tunnel.client; using linker.messenger.tuntap.cidr; +using linker.messenger.tuntap.client; using linker.messenger.tuntap.lease; using linker.messenger.tuntap.messenger; using linker.nat; diff --git a/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterExcludeIP.cs b/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterExcludeIP.cs index a8e03043..d36cf443 100644 --- a/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterExcludeIP.cs +++ b/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterExcludeIP.cs @@ -1,5 +1,6 @@ using linker.messenger.exroute; -using linker.messenger.tunnel; +using linker.messenger.tunnel.client; +using linker.messenger.tuntap.client; using linker.tunnel.transport; using System.Net; diff --git a/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterManager.cs b/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterManager.cs index b6a373e4..cdda3cdf 100644 --- a/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterManager.cs +++ b/src/linker.messenger.tuntap/cidr/TuntapCidrDecenterManager.cs @@ -1,6 +1,7 @@ using linker.libs; using linker.messenger.exroute; using linker.messenger.signin; +using linker.messenger.tuntap.client; using linker.tun.device; using System.Net; diff --git a/src/linker.messenger.tuntap/cidr/TuntapCidrMapfileManager.cs b/src/linker.messenger.tuntap/cidr/TuntapCidrMapfileManager.cs index 56a1441b..0ef0f080 100644 --- a/src/linker.messenger.tuntap/cidr/TuntapCidrMapfileManager.cs +++ b/src/linker.messenger.tuntap/cidr/TuntapCidrMapfileManager.cs @@ -1,4 +1,5 @@ using linker.libs; +using linker.messenger.tuntap.client; using System.Net; namespace linker.messenger.tuntap.cidr diff --git a/src/linker.messenger.tuntap/ITuntapClientStore.cs b/src/linker.messenger.tuntap/client/ITuntapClientStore.cs similarity index 82% rename from src/linker.messenger.tuntap/ITuntapClientStore.cs rename to src/linker.messenger.tuntap/client/ITuntapClientStore.cs index 7907f8c3..4dfbbd1d 100644 --- a/src/linker.messenger.tuntap/ITuntapClientStore.cs +++ b/src/linker.messenger.tuntap/client/ITuntapClientStore.cs @@ -1,5 +1,4 @@ - -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public interface ITuntapClientStore { diff --git a/src/linker.messenger.tuntap/ITuntapSystemInformation.cs b/src/linker.messenger.tuntap/client/ITuntapSystemInformation.cs similarity index 93% rename from src/linker.messenger.tuntap/ITuntapSystemInformation.cs rename to src/linker.messenger.tuntap/client/ITuntapSystemInformation.cs index 0c3898d6..76ff26f2 100644 --- a/src/linker.messenger.tuntap/ITuntapSystemInformation.cs +++ b/src/linker.messenger.tuntap/client/ITuntapSystemInformation.cs @@ -1,5 +1,4 @@ - -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public interface ITuntapSystemInformation { diff --git a/src/linker.messenger.tuntap/TuntapAdapter.cs b/src/linker.messenger.tuntap/client/TuntapAdapter.cs similarity index 99% rename from src/linker.messenger.tuntap/TuntapAdapter.cs rename to src/linker.messenger.tuntap/client/TuntapAdapter.cs index cceb03bd..38f81027 100644 --- a/src/linker.messenger.tuntap/TuntapAdapter.cs +++ b/src/linker.messenger.tuntap/client/TuntapAdapter.cs @@ -9,7 +9,7 @@ using linker.tunnel.connection; using System.Net; using static linker.nat.LinkerDstMapping; -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public sealed class TuntapAdapter : ILinkerTunDeviceCallback, ITuntapProxyCallback { diff --git a/src/linker.messenger.tuntap/TuntapApiController.cs b/src/linker.messenger.tuntap/client/TuntapApiController.cs similarity index 99% rename from src/linker.messenger.tuntap/TuntapApiController.cs rename to src/linker.messenger.tuntap/client/TuntapApiController.cs index 23928eda..c91ba5c7 100644 --- a/src/linker.messenger.tuntap/TuntapApiController.cs +++ b/src/linker.messenger.tuntap/client/TuntapApiController.cs @@ -9,7 +9,7 @@ using linker.messenger.tuntap.messenger; using linker.libs.web; using linker.messenger.tuntap.cidr; -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public sealed class TuntapApiController : IApiController { diff --git a/src/linker.messenger.tuntap/TuntapConfigTransfer.cs b/src/linker.messenger.tuntap/client/TuntapConfigTransfer.cs similarity index 99% rename from src/linker.messenger.tuntap/TuntapConfigTransfer.cs rename to src/linker.messenger.tuntap/client/TuntapConfigTransfer.cs index 0c2b9d6b..fdab67a9 100644 --- a/src/linker.messenger.tuntap/TuntapConfigTransfer.cs +++ b/src/linker.messenger.tuntap/client/TuntapConfigTransfer.cs @@ -5,7 +5,7 @@ using linker.messenger.signin; using linker.messenger.tuntap.lease; using linker.libs.timer; -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public sealed class TuntapConfigTransfer { diff --git a/src/linker.messenger.tuntap/TuntapDecenter.cs b/src/linker.messenger.tuntap/client/TuntapDecenter.cs similarity index 99% rename from src/linker.messenger.tuntap/TuntapDecenter.cs rename to src/linker.messenger.tuntap/client/TuntapDecenter.cs index 7fab4841..ec826827 100644 --- a/src/linker.messenger.tuntap/TuntapDecenter.cs +++ b/src/linker.messenger.tuntap/client/TuntapDecenter.cs @@ -5,7 +5,7 @@ using linker.messenger.signin; using System.Net; using linker.libs.timer; -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public sealed class TuntapDecenter : IDecenter { diff --git a/src/linker.messenger.tuntap/TuntapPingTransfer.cs b/src/linker.messenger.tuntap/client/TuntapPingTransfer.cs similarity index 98% rename from src/linker.messenger.tuntap/TuntapPingTransfer.cs rename to src/linker.messenger.tuntap/client/TuntapPingTransfer.cs index 8f859ca8..302b90e2 100644 --- a/src/linker.messenger.tuntap/TuntapPingTransfer.cs +++ b/src/linker.messenger.tuntap/client/TuntapPingTransfer.cs @@ -7,7 +7,7 @@ using linker.libs.extends; using linker.messenger.signin; using linker.libs.timer; -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public sealed class TuntapPingTransfer { diff --git a/src/linker.messenger.tuntap/TuntapProxy.cs b/src/linker.messenger.tuntap/client/TuntapProxy.cs similarity index 99% rename from src/linker.messenger.tuntap/TuntapProxy.cs rename to src/linker.messenger.tuntap/client/TuntapProxy.cs index f6f5a18a..df66251e 100644 --- a/src/linker.messenger.tuntap/TuntapProxy.cs +++ b/src/linker.messenger.tuntap/client/TuntapProxy.cs @@ -9,7 +9,7 @@ using linker.tunnel; using linker.tunnel.connection; using System.Buffers.Binary; -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public interface ITuntapProxyCallback { diff --git a/src/linker.messenger.tuntap/TuntapTransfer.cs b/src/linker.messenger.tuntap/client/TuntapTransfer.cs similarity index 99% rename from src/linker.messenger.tuntap/TuntapTransfer.cs rename to src/linker.messenger.tuntap/client/TuntapTransfer.cs index b80fa97e..c9fb1e97 100644 --- a/src/linker.messenger.tuntap/TuntapTransfer.cs +++ b/src/linker.messenger.tuntap/client/TuntapTransfer.cs @@ -5,7 +5,7 @@ using linker.libs.timer; using static linker.nat.LinkerDstMapping; using linker.tun.device; -namespace linker.messenger.tuntap +namespace linker.messenger.tuntap.client { public sealed class TuntapTransfer { diff --git a/src/linker.messenger.tuntap/messenger/TuntapMessenger.cs b/src/linker.messenger.tuntap/messenger/TuntapMessenger.cs index 38fff445..1abdea73 100644 --- a/src/linker.messenger.tuntap/messenger/TuntapMessenger.cs +++ b/src/linker.messenger.tuntap/messenger/TuntapMessenger.cs @@ -1,6 +1,7 @@ using linker.libs; using linker.messenger.signin; using linker.messenger.tuntap.cidr; +using linker.messenger.tuntap.client; using linker.messenger.tuntap.lease; namespace linker.messenger.tuntap.messenger diff --git a/src/linker.messenger.updater/UpdaterMessengerIds.cs b/src/linker.messenger.updater/UpdaterMessengerIds.cs index 6d34c150..c736e15d 100644 --- a/src/linker.messenger.updater/UpdaterMessengerIds.cs +++ b/src/linker.messenger.updater/UpdaterMessengerIds.cs @@ -31,7 +31,7 @@ UpdateServer170 = 2617, UpdateServer184 = 2619, - UpdateServer186 = 2020, + UpdateServer186 = 2620, Max = 2299 } diff --git a/src/linker.tunnel/TunnelUpnpTransfer.cs b/src/linker.tunnel/TunnelUpnpTransfer.cs index ddc7647e..e361f05e 100644 --- a/src/linker.tunnel/TunnelUpnpTransfer.cs +++ b/src/linker.tunnel/TunnelUpnpTransfer.cs @@ -1,7 +1,6 @@ -using linker.libs.timer; -using linker.tunnel.transport; -using Mono.Nat; -using System.Collections.Concurrent; +using linker.tunnel.transport; +using linker.upnp; +using System.Net.Sockets; namespace linker.tunnel { @@ -10,10 +9,6 @@ namespace linker.tunnel /// public sealed class TunnelUpnpTransfer { - - private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1); - private readonly ConcurrentDictionary natDevices = new ConcurrentDictionary(); - public MapInfo MapInfo { get; private set; } public MapInfo MapInfo1 { get; private set; } @@ -26,78 +21,7 @@ namespace linker.tunnel this.transportUdpPortMap = transportUdpPortMap; this.transportTcpPortMap = transportTcpPortMap; - NatUtility.DeviceFound += DeviceFound; - NatUtility.StartDiscovery(); - LoopDiscovery(); - - } - - private static void LoopDiscovery() - { - TimerHelper.SetIntervalLong(() => - { - NatUtility.StopDiscovery(); - NatUtility.StartDiscovery(); - }, 60 * 1000); - } - private void DeviceFound(object sender, DeviceEventArgs args) - { - INatDevice device = args.Device; - - natDevices.AddOrUpdate(device.NatProtocol, device, (a, b) => device); - - AddMap(); - } - private void AddMap() - { - if (natDevices.IsEmpty || MapInfo == null) return; - - TimerHelper.Async(async () => - { - await locker.WaitAsync().ConfigureAwait(false); - - foreach (var device in natDevices.Values) - { - try - { - if (await HasMap(device, Protocol.Tcp, MapInfo.PublicPort).ConfigureAwait(false) == false) - { - Mapping mapping = new Mapping(Protocol.Tcp, MapInfo.PrivatePort, MapInfo.PublicPort, 7 * 24 * 60 * 60, $"linker-tcp-{MapInfo.PublicPort}-{MapInfo.PrivatePort}"); - await device.CreatePortMapAsync(mapping).ConfigureAwait(false); - Mapping m = await device.GetSpecificMappingAsync(Protocol.Tcp, mapping.PublicPort).ConfigureAwait(false); - } - } - catch - { - } - - try - { - if (await HasMap(device, Protocol.Udp, MapInfo.PublicPort).ConfigureAwait(false) == false) - { - Mapping mapping = new Mapping(Protocol.Udp, MapInfo.PrivatePort, MapInfo.PublicPort, 7 * 24 * 60 * 60, $"linker-udp-{MapInfo.PublicPort}-{MapInfo.PrivatePort}"); - await device.CreatePortMapAsync(mapping).ConfigureAwait(false); - Mapping m = await device.GetSpecificMappingAsync(Protocol.Udp, mapping.PublicPort).ConfigureAwait(false); - } - } - catch - { - } - } - locker.Release(); - }); - } - private async Task HasMap(INatDevice device, Protocol protocol, int publicPort) - { - try - { - Mapping m = await device.GetSpecificMappingAsync(protocol, publicPort).ConfigureAwait(false); - return (DateTime.Now - m.Expiration).TotalSeconds > 2 * 60; - } - catch (Exception) - { - } - return false; + PortMappingUtility.StartDiscovery(); } /// @@ -107,10 +31,14 @@ namespace linker.tunnel public void SetMap(int privatePort) { MapInfo = new MapInfo { PrivatePort = privatePort, PublicPort = privatePort }; - AddMap(); - _ = transportTcpPortMap.Listen(privatePort); - _ = transportUdpPortMap.Listen(privatePort); + PortMappingInfo tcp = new PortMappingInfo { PrivatePort = privatePort, PublicPort = privatePort, ProtocolType = ProtocolType.Tcp, Description = $"linker tunnel tcp", DeviceType = DeviceType.Pmp, LeaseDuration = 7 * 24 * 60 * 60, Deletable = false }; + _ = PortMappingUtility.Add(tcp).ConfigureAwait(false); + PortMappingInfo udp = new PortMappingInfo { PrivatePort = privatePort, PublicPort = privatePort, ProtocolType = ProtocolType.Udp, Description = $"linker tunnel udp", DeviceType = DeviceType.Pmp, LeaseDuration = 7 * 24 * 60 * 60, Deletable = false }; + _ = PortMappingUtility.Add(udp).ConfigureAwait(false); + + _ = transportTcpPortMap.Listen(privatePort).ConfigureAwait(false); + _ = transportUdpPortMap.Listen(privatePort).ConfigureAwait(false); } /// /// 设置端口映射,内网端口和外网端口不一样 @@ -120,9 +48,13 @@ namespace linker.tunnel public void SetMap(int privatePort, int publicPort) { MapInfo1 = new MapInfo { PrivatePort = privatePort, PublicPort = publicPort }; + PortMappingInfo tcp = new PortMappingInfo { PrivatePort = privatePort, PublicPort = publicPort, ProtocolType = ProtocolType.Tcp, Description = $"linker tunnel tcp", DeviceType = DeviceType.Pmp, LeaseDuration = 7 * 24 * 60 * 60, Deletable = false }; + _ = PortMappingUtility.Add(tcp).ConfigureAwait(false); + PortMappingInfo udp = new PortMappingInfo { PrivatePort = privatePort, PublicPort = publicPort, ProtocolType = ProtocolType.Udp, Description = $"linker tunnel udp", DeviceType = DeviceType.Pmp, LeaseDuration = 7 * 24 * 60 * 60, Deletable = false }; + _ = PortMappingUtility.Add(udp).ConfigureAwait(false); - _ = transportTcpPortMap.Listen(privatePort); - _ = transportUdpPortMap.Listen(privatePort); + _ = transportTcpPortMap.Listen(privatePort).ConfigureAwait(false); + _ = transportUdpPortMap.Listen(privatePort).ConfigureAwait(false); } } diff --git a/src/linker.tunnel/linker.tunnel.csproj b/src/linker.tunnel/linker.tunnel.csproj index d21d8fe5..ee67f2d7 100644 --- a/src/linker.tunnel/linker.tunnel.csproj +++ b/src/linker.tunnel/linker.tunnel.csproj @@ -37,12 +37,12 @@ embedded - + diff --git a/src/linker.tunnel/wanport/TunnelWanPortTransfer.cs b/src/linker.tunnel/wanport/TunnelWanPortTransfer.cs index ab3ae27d..641c186f 100644 --- a/src/linker.tunnel/wanport/TunnelWanPortTransfer.cs +++ b/src/linker.tunnel/wanport/TunnelWanPortTransfer.cs @@ -1,5 +1,4 @@ using linker.libs; -using Mono.Nat; using System.Net; namespace linker.tunnel.wanport diff --git a/src/linker.upnp/IPortMappingDevice.cs b/src/linker.upnp/IPortMappingDevice.cs new file mode 100644 index 00000000..32f72b32 --- /dev/null +++ b/src/linker.upnp/IPortMappingDevice.cs @@ -0,0 +1,84 @@ +using System.Net; +using System.Net.Sockets; + +namespace linker.upnp +{ + public enum DeviceType : byte + { + Upnp = 1, + Pmp = 2, + Pcp = 4, + All = 255 + } + public sealed class PortMappingInfo + { + /// + /// 内网ip + /// + public IPAddress ClientIp { get; set; } + /// + /// 外网端口 + /// + public int PublicPort { get; set; } + /// + /// 内网端口 + /// + public int PrivatePort { get; set; } + /// + /// 协议 + /// + public ProtocolType ProtocolType { get; set; } + /// + /// 启用 + /// + public bool Enabled { get; set; } = true; + /// + /// 描述 + /// + public string Description { get; set; } + /// + /// 存活 + /// + public int LeaseDuration { get; set; } + + /// + /// 设备类型 + /// + public DeviceType DeviceType { get; set; } = DeviceType.All; + + public bool Deletable { get; set; } = true; + + public override string ToString() + { + return $"外网[*:{PublicPort}]->内网[{ClientIp}:{PrivatePort}]->启用:{Enabled}->协议:{ProtocolType}->存活:{LeaseDuration}->描述:{Description}"; + } + } + + public interface IPortMappingDevice + { + /// + /// 类型 + /// + public DeviceType Type { get; } + public IPAddress GatewayIp { get; set; } + public IPAddress WanIp { get; set; } + /// + /// 获取设备所有映射 + /// + /// + public Task> Get(); + /// + /// 添加映射 + /// + /// + /// + public Task Add(PortMappingInfo mapping); + /// + /// 删除映射 + /// + /// + /// + /// + public Task Delete(int publicPort, ProtocolType protocol); + } +} diff --git a/src/linker.upnp/IPortMappingService.cs b/src/linker.upnp/IPortMappingService.cs new file mode 100644 index 00000000..7423d0a0 --- /dev/null +++ b/src/linker.upnp/IPortMappingService.cs @@ -0,0 +1,42 @@ +using System.Net.Sockets; + +namespace linker.upnp +{ + public interface IPortMappingService + { + public DeviceType Type { get; } + + /// + /// 发现 + /// + /// + /// + public Task> Discovery(CancellationToken token); + + /// + /// 获取本类型的所有已发现的设备 + /// + /// + public List GetDevices(); + + /// + /// 获取所有设备的所有映射信息 + /// + /// + public Task> Get(); + /// + /// 添加一条映射 + /// + /// + /// + public Task Add(PortMappingInfo mapping); + /// + /// 删除一条映射 + /// + /// + /// + /// + public Task Delete(int publicPort, ProtocolType protocol); + } + +} diff --git a/src/linker.upnp/PortMappingPmpService.cs b/src/linker.upnp/PortMappingPmpService.cs new file mode 100644 index 00000000..afce06ad --- /dev/null +++ b/src/linker.upnp/PortMappingPmpService.cs @@ -0,0 +1,223 @@ +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace linker.upnp +{ + public sealed class PmpDevice : IPortMappingDevice + { + public DeviceType Type => DeviceType.Pmp; + + + public IPAddress GatewayIp { get; set; } + public IPAddress WanIp { get; set; } + + + public async Task> Get() + { + return []; + } + public async Task Add(PortMappingInfo mapping) + { + if ((mapping.DeviceType & Type) != Type) return false; + + byte[] bytes = new byte[12]; + bytes[0] = 0; + bytes[1] = (byte)(mapping.ProtocolType == ProtocolType.Tcp ? 2 : 1); + bytes[2] = 0; + bytes[3] = 0; + + BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(4), (ushort)mapping.PrivatePort); + BinaryPrimitives.WriteUInt16BigEndian(bytes.AsSpan(6), (ushort)mapping.PublicPort); + BinaryPrimitives.WriteInt32BigEndian(bytes.AsSpan(8), mapping.LeaseDuration); + + + using UdpClient client = CreateClient(); + try + { + + for (int i = 0; i < 3; i++) + { + await client.SendAsync(bytes, bytes.Length, new IPEndPoint(GatewayIp, 5351)).ConfigureAwait(false); + } + var _cts = new CancellationTokenSource(3000); + while (_cts.IsCancellationRequested == false) + { + UdpReceiveResult result = await client.ReceiveAsync(_cts.Token).ConfigureAwait(false); + byte version = result.Buffer[0]; + byte opcode = result.Buffer[1]; + ushort code = BinaryPrimitives.ReadUInt16BigEndian(result.Buffer.AsSpan(2)); + uint time = BinaryPrimitives.ReadUInt32BigEndian(result.Buffer.AsSpan(4)); + ushort privatePort = BinaryPrimitives.ReadUInt16BigEndian(result.Buffer.AsSpan(8)); + ushort publicPort = BinaryPrimitives.ReadUInt16BigEndian(result.Buffer.AsSpan(10)); + uint lease = BinaryPrimitives.ReadUInt32BigEndian(result.Buffer.AsSpan(12)); + + if (code == 0) + { + return true; + } + } + } + catch (Exception) + { + } + client?.Dispose(); + + return false; + } + public async Task Delete(int publicPort, ProtocolType protocol) + { + return true; + } + + private UdpClient CreateClient() + { + UdpClient client = new UdpClient(); + client.Client.SetSocketOption(SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, true); + client.Client.Bind(new IPEndPoint(IPAddress.Any, 5350)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + try + { + const uint IOC_IN = 0x80000000; + int IOC_VENDOR = 0x18000000; + int SIO_UDP_CONNRESET = (int)(IOC_IN | IOC_VENDOR | 12); + client.Client.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); + } + catch (Exception) + { + } + } + return client; + } + + public override string ToString() + { + return $"PMP:{GatewayIp}->{WanIp}"; + } + } + public sealed class PortMappingPmpService : IPortMappingService + { + public DeviceType Type => DeviceType.Pmp; + + private readonly ConcurrentDictionary pmpDevices = new(); + + public async Task> Discovery(CancellationToken token) + { + try + { + List> tasks = GetGatewayIp().Select(async (ip) => + { + using UdpClient client = CreateClient(); + try + { + + for (int i = 0; i < 3; i++) + { + await client.SendAsync([0, 0], 2, new IPEndPoint(ip, 5351)).ConfigureAwait(false); + } + var _cts = new CancellationTokenSource(3000); + while (token.IsCancellationRequested == false && _cts.Token.IsCancellationRequested == false) + { + UdpReceiveResult result = await client.ReceiveAsync(_cts.Token).ConfigureAwait(false); + byte version = result.Buffer[0]; + byte opcode = result.Buffer[1]; + ushort code = BinaryPrimitives.ReadUInt16BigEndian(result.Buffer.AsSpan(2)); + uint time = BinaryPrimitives.ReadUInt32BigEndian(result.Buffer.AsSpan(4)); + IPAddress wanip = new IPAddress(result.Buffer.AsSpan(8)); + + if (code == 0) + { + return new PmpDevice + { + GatewayIp = ip, + WanIp = wanip + }; + } + } + } + catch (Exception) + { + } + client?.Dispose(); + + return null; + + }).ToList(); + var responses = await Task.WhenAll(tasks).ConfigureAwait(false); + foreach (var device in responses.Where(c => c != null)) + { + string key = device.GatewayIp.ToString(); + if(pmpDevices.TryGetValue(key,out IPortMappingDevice _device) == false) + { + _device = device; + pmpDevices.TryAdd(key, device); + } + } + return pmpDevices.Values.ToList(); + } + catch (Exception) + { + } + return []; + } + + private List GetGatewayIp() + { + return NetworkInterface.GetAllNetworkInterfaces().Where(c => c.OperationalStatus == OperationalStatus.Up) + .SelectMany(c => c.GetIPProperties().GatewayAddresses) + .Select(c => c.Address).Where(c => c.AddressFamily == AddressFamily.InterNetwork) + .Where(c => c.Equals(IPAddress.Loopback) == false).ToList(); + } + private UdpClient CreateClient() + { + UdpClient client = new UdpClient(); + client.Client.SetSocketOption(SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, true); + client.Client.Bind(new IPEndPoint(IPAddress.Any, 5350)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + try + { + const uint IOC_IN = 0x80000000; + int IOC_VENDOR = 0x18000000; + int SIO_UDP_CONNRESET = (int)(IOC_IN | IOC_VENDOR | 12); + client.Client.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); + } + catch (Exception) + { + } + } + return client; + } + + public List GetDevices() + { + return pmpDevices.Values.ToList(); + } + + public async Task> Get() + { + return []; + } + public async Task Add(PortMappingInfo mapping) + { + foreach (var device in pmpDevices.Values) + { + await device.Add(mapping).ConfigureAwait(false); + } + } + public async Task Delete(int publicPort, ProtocolType protocol) + { + foreach (var device in pmpDevices.Values) + { + await device.Delete(publicPort, protocol).ConfigureAwait(false); + } + } + + } +} diff --git a/src/linker.upnp/PortMappingUpnpService.cs b/src/linker.upnp/PortMappingUpnpService.cs new file mode 100644 index 00000000..1a17b7da --- /dev/null +++ b/src/linker.upnp/PortMappingUpnpService.cs @@ -0,0 +1,357 @@ +using System.Collections.Concurrent; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Xml; + +namespace linker.upnp +{ + public sealed class UpnpDevice : IPortMappingDevice + { + /// + /// 设备类型 + /// + public DeviceType Type => DeviceType.Upnp; + public IPAddress GatewayIp { get; set; } + public IPAddress WanIp { get; set; } + + /// + /// 服务类型描述 + /// + public string ServiceType { get; set; } + /// + /// 控制地址 + /// + public string ControlUrl { get; set; } + /// + /// 内网ip + /// + public IPAddress ClientIp { get; set; } + public async Task> Get() + { + List result = new List(); + using HttpClient httpClient = new HttpClient(); + for (int index = 0; ; index++) + { + string action = BuildGetPortMappingRequest(index); + + try + { + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, ControlUrl); + request.Headers.Add("SOAPACTION", $"\"{ServiceType}#GetGenericPortMappingEntry\""); + request.Content = new StringContent(action, Encoding.UTF8, "text/xml"); + using HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false); + string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + if (responseContent.Contains("UPnPError")) + { + break; + } + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(responseContent); + XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/"); + nsmgr.AddNamespace("u", "urn:schemas-upnp-org:service:WANIPConnection:1"); + XmlNode resp = doc.SelectSingleNode("//u:GetGenericPortMappingEntryResponse", nsmgr); + + result.Add(new PortMappingInfo + { + ClientIp = IPAddress.Parse(resp.SelectSingleNode("NewInternalClient").InnerText), + Description = resp.SelectSingleNode("NewPortMappingDescription").InnerText, + Enabled = resp.SelectSingleNode("NewEnabled").InnerText == "1", + LeaseDuration = int.Parse(resp.SelectSingleNode("NewLeaseDuration").InnerText), + PrivatePort = int.Parse(resp.SelectSingleNode("NewInternalPort").InnerText), + PublicPort = int.Parse(resp.SelectSingleNode("NewExternalPort").InnerText), + ProtocolType = resp.SelectSingleNode("NewProtocol").InnerText == "TCP" ? ProtocolType.Tcp : ProtocolType.Udp, + DeviceType = DeviceType.Upnp + }); + } + catch (Exception) + { + } + + } + return result; + } + public async Task Add(PortMappingInfo mapping) + { + if ((mapping.DeviceType & Type) != Type) return false; + + string action = BuildAddPortMappingRequest(mapping); + + using HttpClient httpClient = new HttpClient(); + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, ControlUrl); + request.Headers.Add("SOAPACTION", $"\"{ServiceType}#AddPortMapping\""); + request.Content = new StringContent(action, Encoding.UTF8, "text/xml"); + using HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false); + string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return responseContent.Contains("AddPortMappingResponse"); + } + public async Task Delete(int publicPort, ProtocolType protocol) + { + string action = BuildDeletePortMappingRequest(publicPort, protocol); + using HttpClient httpClient = new HttpClient(); + using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, ControlUrl); + request.Headers.Add("SOAPACTION", $"\"{ServiceType}#DeletePortMapping\""); + request.Content = new StringContent(action, Encoding.UTF8, "text/xml"); + using HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false); + string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + return responseContent.Contains("DeletePortMappingResponse"); + } + + private string BuildAddPortMappingRequest(PortMappingInfo mapping) + { + if (mapping.ClientIp == null || mapping.ClientIp.Equals(IPAddress.Any)) + { + mapping.ClientIp = ClientIp; + } + return $@" + + + + + {mapping.PublicPort} + {mapping.ProtocolType.ToString().ToUpper()} + {mapping.PrivatePort} + {mapping.ClientIp} + {(mapping.Enabled ? "1" : "0")} + {mapping.Description} + {mapping.LeaseDuration} + + + "; + } + private string BuildDeletePortMappingRequest(int publicPort, ProtocolType protocol) + { + return $@" + + + + + {publicPort} + {protocol.ToString().ToUpper()} + + +"; + } + private string BuildGetPortMappingRequest(int index) + { + return $@" + + + + {index} + + +"; + } + + public override string ToString() + { + return $"类型:UPNP->客户端ip:{ClientIp}->控制地址:{ControlUrl}->服务描述:{ServiceType}"; + } + + } + public sealed class PortMappingUpnpService : IPortMappingService + { + public DeviceType Type => DeviceType.Upnp; + + + private readonly ConcurrentDictionary upnpDevices = new(); + private CancellationTokenSource cts; + private const string discoveryMessage = + "M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "MX: 3\r\n" + + "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" + + "USER-AGENT: UPnP/1.0\r\n" + + "\r\n"; + private byte[] messageBytes = Encoding.ASCII.GetBytes(discoveryMessage); + + public async Task> Discovery(CancellationToken token) + { + try + { + List> tasks = GetLocalIp().Select(async (ip) => + { + using UdpClient client = CreateClient(ip); + try + { + + for (int i = 0; i < 3; i++) + { + await client.SendAsync(messageBytes, messageBytes.Length, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900)).ConfigureAwait(false); + } + var _cts = new CancellationTokenSource(3000); + while (token.IsCancellationRequested == false && _cts.Token.IsCancellationRequested == false) + { + UdpReceiveResult result = await client.ReceiveAsync(_cts.Token).ConfigureAwait(false); + + return (Encoding.ASCII.GetString(result.Buffer), ip, (result.RemoteEndPoint as IPEndPoint).Address); + } + } + catch (Exception) + { + } + client?.Dispose(); + return (string.Empty, ip, IPAddress.Any); + }).ToList(); + var responses = await Task.WhenAll(tasks).ConfigureAwait(false); + foreach (var response in responses.Where(c => string.IsNullOrWhiteSpace(c.response) == false)) + { + UpnpDevice device = await PrarseDevice(response.response, response.ip, response.gateway).ConfigureAwait(false); + if (device != null) + { + if (upnpDevices.TryGetValue(device.ControlUrl, out IPortMappingDevice _device) == false) + { + _device = device; + upnpDevices.TryAdd(device.ControlUrl, device); + } + } + } + return upnpDevices.Values.ToList(); + } + catch (Exception) + { + } + return []; + } + + + + private async Task PrarseDevice(string respStr, IPAddress clientIp, IPAddress gateway) + { + try + { + int start = respStr.AsSpan().IndexOf("LOCATION: "); + int end = respStr.AsSpan().Slice(start + 10).IndexOf("\r\n"); + string location = respStr.AsSpan().Slice(start + 10, end).ToString(); + + using HttpClient webClient = new HttpClient(); + string resp = await webClient.GetStringAsync(location).ConfigureAwait(false); + + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(resp); + XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable); + ns.AddNamespace("upnp", "urn:schemas-upnp-org:device-1-0"); + var serviceNodes = doc.SelectNodes("//upnp:service", ns); + foreach (XmlNode service in serviceNodes) + { + string serviceType = service.SelectSingleNode("upnp:serviceType", ns)?.InnerText; + if (string.IsNullOrWhiteSpace(serviceType) || (serviceType.Contains("WANIPConnection") == false && serviceType.Contains("WANPPPConnection") == false)) + { + continue; + } + string controlUrl = service.SelectSingleNode("upnp:controlURL", ns)?.InnerText; + if (string.IsNullOrWhiteSpace(controlUrl)) + { + continue; + } + + Uri baseUri = new Uri(location); + if (!controlUrl.StartsWith("http")) + { + controlUrl = new Uri(baseUri, controlUrl).ToString(); + } + return new UpnpDevice + { + ServiceType = serviceType, + ControlUrl = controlUrl, + ClientIp = clientIp, + GatewayIp = gateway, + WanIp = gateway + }; + } + } + catch (Exception) + { + } + return null; + } + private List GetLocalIp() + { + return NetworkInterface.GetAllNetworkInterfaces().Where(c => c.OperationalStatus == OperationalStatus.Up) + .SelectMany(c => c.GetIPProperties().UnicastAddresses) + .Select(c => c.Address).Where(c => c.AddressFamily == AddressFamily.InterNetwork) + .Where(c => c.Equals(IPAddress.Loopback) == false).ToList(); + } + private UdpClient CreateClient(IPAddress ip) + { + UdpClient client = new UdpClient(); + client.Client.SetSocketOption(SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, true); + client.Client.Bind(new IPEndPoint(ip, 0)); + client.JoinMulticastGroup(IPAddress.Parse("239.255.255.250")); + client.Client.SetSocketOption(SocketOptionLevel.IP, + SocketOptionName.MulticastTimeToLive, 4); + client.Client.SetSocketOption(SocketOptionLevel.IP, + SocketOptionName.MulticastLoopback, true); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + try + { + const uint IOC_IN = 0x80000000; + int IOC_VENDOR = 0x18000000; + int SIO_UDP_CONNRESET = (int)(IOC_IN | IOC_VENDOR | 12); + client.Client.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); + } + catch (Exception) + { + } + } + return client; + } + + /// + /// 获取所有已找到的upnp设备 + /// + /// + public List GetDevices() + { + return upnpDevices.Values.ToList(); + } + + /// + /// 获取所有upnp设备的所有映射信息 + /// + /// + public async Task> Get() + { + return (await Task.WhenAll(upnpDevices.Values.Select(c => c.Get()).ToList()).ConfigureAwait(false)).SelectMany(c => c).ToList(); + } + /// + /// 添加一条映射 + /// + /// + /// + public async Task Add(PortMappingInfo mapping) + { + foreach (var device in upnpDevices.Values) + { + await device.Add(mapping).ConfigureAwait(false); + } + } + /// + /// 删除一条映射 + /// + /// + /// + /// + public async Task Delete(int publicPort, ProtocolType protocol) + { + foreach (var device in upnpDevices.Values) + { + await device.Delete(publicPort, protocol).ConfigureAwait(false); + } + } + } +} diff --git a/src/linker.upnp/PortMappingUtility.cs b/src/linker.upnp/PortMappingUtility.cs new file mode 100644 index 00000000..07856f59 --- /dev/null +++ b/src/linker.upnp/PortMappingUtility.cs @@ -0,0 +1,290 @@ +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; + +namespace linker.upnp +{ + public static class PortMappingUtility + { + /// + /// 当发现设备 + /// + public static event Action OnDeviceFound + { + add => DeviceFound += value; + remove => DeviceFound -= value; + } + private static Action DeviceFound = (device) => { }; + + /// + /// 当发生变化,发现设备,添加删除映射什么的 + /// + public static event Action OnChange + { + add => Change += value; + remove => Change -= value; + } + private static Action Change = () => { }; + + /// + /// 设备数 + /// + public static int DeviceCount => devices.Count; + /// + /// 映射数 + /// + public static int MappingCount => mappings.Count; + /// + /// 本地数 + /// + public static int LocalMappingCount => localMappings.Where(c => c.Value.Deleted == false).Count(); + /// + /// 外网数量 + /// + public static int WanCount => devices.Values.Count(c => IsPrivateIP(c.WanIp) == false); + + + private static readonly IPortMappingService[] services = [new PortMappingUpnpService(), new PortMappingPmpService()]; + private static readonly ConcurrentDictionary<(IPAddress gateway, DeviceType deviceType), IPortMappingDevice> devices = new(); + private static readonly ConcurrentDictionary<(int publicPort, ProtocolType protocolType), MappingCacheInfo> localMappings = new(); + private static List mappings = []; + + private static CancellationTokenSource cts; + private static CancellationTokenSource _cts; + private static long ticks = Environment.TickCount64; + + + /// + /// 开始发现设备 + /// + /// + public static void StartDiscovery(DeviceType deviceType = DeviceType.All) + { + if (cts != null && cts.Token.IsCancellationRequested == false) + { + return; + } + cts = new CancellationTokenSource(); + + Task.Run(async () => + { + try + { + while (cts.IsCancellationRequested == false) + { + var _services = services.Where(service => (service.Type & deviceType) == service.Type).Select(c => c.Discovery(cts.Token)).ToList(); + await Task.WhenAll(_services).ConfigureAwait(false); + + RefreshDevice(); + + await Add().ConfigureAwait(false); + await Delete().ConfigureAwait(false); + await RefreshMappings().ConfigureAwait(false); + await Delay().ConfigureAwait(false); + } + } + catch (Exception) + { + } + }); + } + private static void RefreshDevice() + { + bool changed = false; + foreach (var device in services.Select(c => c.GetDevices()).ToList().SelectMany(c => c)) + { + if (devices.TryGetValue((device.GatewayIp, device.Type), out IPortMappingDevice _device) == false) + { + changed = true; + devices.TryAdd((device.GatewayIp, device.Type), device); + DeviceFound?.Invoke(device); + } + } + if (changed) + { + Change?.Invoke(); + } + } + private static async Task RefreshMappings() + { + int count = mappings.Count; + mappings = (await Task.WhenAll(services.Select(c => c.Get()).ToList()).ConfigureAwait(false)) + .SelectMany(c => c) + .Where(c => localMappings.TryGetValue((c.PublicPort, c.ProtocolType), out MappingCacheInfo cache) == false || cache.Deleted == false) + .ToList(); + + if (mappings.Count != count) + { + Change?.Invoke(); + } + } + private static async Task Delay() + { + _cts = new CancellationTokenSource(); + try + { + int delay = Math.Max((int)Math.Min(30000, Environment.TickCount64 - ticks), 3000); + await Task.Delay(delay, _cts.Token).ConfigureAwait(false); + } + catch + { + } + finally + { + _cts.Cancel(); + _cts = new CancellationTokenSource(); + } + } + private static void RefreshDelay() + { + ticks = Environment.TickCount64; + _cts?.Cancel(); + } + + /// + /// 停止发现设备 + /// + public static void StopDiscovery() + { + cts?.Cancel(); + _cts?.Cancel(); + } + + /// + /// 获取所有已发现设备 + /// + /// + public static List GetDevices() + { + RefreshDelay(); + return services.Select(c => c.GetDevices()).ToList().SelectMany(c => c).ToList(); + } + + /// + /// 获取所有已发现设备的所有映射信息 + /// + /// + public static List Get() + { + RefreshDelay(); + return mappings + .Where(c => localMappings.TryGetValue((c.PublicPort, c.ProtocolType), out MappingCacheInfo cache) == false || cache.Deleted == false) + .ToList(); + } + /// + /// 获取本地添加的映射信息(不包含已删除的) + /// + /// + public static List GetLocal() + { + RefreshDelay(); + return localMappings.Values.Where(c => c.Deleted == false).Select(c => c.Info).ToList(); + } + + /// + /// 添加一条映射 + /// + /// + /// + public static async Task Add(PortMappingInfo mapping) + { + MappingCacheInfo cache = new MappingCacheInfo { Info = mapping }; + localMappings.AddOrUpdate((mapping.PublicPort, mapping.ProtocolType), cache, (k, v) => cache); + await AddInternal(cache.Info).ConfigureAwait(false); + RefreshDelay(); + } + private static async Task Add() + { + foreach (var cache in localMappings.Values.Where(c => c.Deleted == false)) + { + await AddInternal(cache.Info).ConfigureAwait(false); + } + } + private static async Task AddInternal(PortMappingInfo mapping) + { + for (int i = 0; i < services.Length; i++) + { + if ((services[i].Type & mapping.DeviceType) == services[i].Type && services[i].GetDevices().Count > 0) + { + await services[i].Add(mapping).ConfigureAwait(false); + } + } + Change?.Invoke(); + } + + /// + /// 删除一条映射 + /// + /// + /// + /// + public static async Task Delete(int publicPort, ProtocolType protocol) + { + if (localMappings.TryGetValue((publicPort, protocol), out MappingCacheInfo cache)) + { + cache.Deleted = true; + } + else + { + cache = new MappingCacheInfo { Deleted = true, Info = new PortMappingInfo { PublicPort = publicPort, ProtocolType = protocol } }; + localMappings.TryAdd((publicPort, protocol), cache); + } + + for (int i = 0; i < services.Length; i++) + { + if ((services[i].Type & cache.Info.DeviceType) == services[i].Type) + { + await services[i].Delete(publicPort, protocol).ConfigureAwait(false); + } + } + + RefreshDelay(); + Change?.Invoke(); + } + private static async Task Delete() + { + foreach (var cache in localMappings.Values.Where(c => c.Deleted)) + { + for (int i = 0; i < services.Length; i++) + { + if ((services[i].Type & cache.Info.DeviceType) == services[i].Type) + { + await services[i].Delete(cache.Info.PublicPort, cache.Info.ProtocolType).ConfigureAwait(false); + } + } + } + foreach (var key in localMappings.Where(c => c.Value.Deleted).Select(c => c.Key).ToList()) + { + localMappings.TryRemove(key, out _); + } + } + + sealed class MappingCacheInfo + { + public PortMappingInfo Info { get; set; } + public bool Deleted { get; set; } + //public HashSet Added { get; set; } = new HashSet(); + } + + + private static readonly HashSet privateNetworks = new HashSet + { + // IPv4 私有网络 + IPNetwork.Parse("127.0.0.0/8"), // 回环 + IPNetwork.Parse("10.0.0.0/8"), // 私有A类 + IPNetwork.Parse("172.16.0.0/12"), // 私有B类 + IPNetwork.Parse("192.168.0.0/16"), // 私有C类 + IPNetwork.Parse("169.254.0.0/16"), // 链路本地 + IPNetwork.Parse("100.64.0.0/10"), // CGNAT (可选) + + // IPv6 私有网络 + IPNetwork.Parse("fc00::/7"), // ULA + IPNetwork.Parse("fe80::/10"), // 链路本地 + IPNetwork.Parse("::1/128"), // 回环 + }; + private static bool IsPrivateIP(IPAddress ip) + { + return privateNetworks.Any(network => network.Contains(ip)); + } + } +} diff --git a/src/linker.upnp/linker.upnp.csproj b/src/linker.upnp/linker.upnp.csproj new file mode 100644 index 00000000..d439af5a --- /dev/null +++ b/src/linker.upnp/linker.upnp.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + disable + true + false + true + linker upnp + snltty + snltty + linker upnp + snltty + https://github.com/snltty/linker + https://github.com/snltty/linker + linker upnp + 1.9.9 + 1.9.9 + 1.9.9 + + + + full + true + + + full + true + True + + + embedded + + + embedded + + + diff --git a/src/linker.web/public/cloudflare.svg b/src/linker.web/public/cloudflare.svg new file mode 100644 index 00000000..a203bcd5 --- /dev/null +++ b/src/linker.web/public/cloudflare.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/linker.web/public/upnp-green.svg b/src/linker.web/public/upnp-green.svg new file mode 100644 index 00000000..d34b36c8 --- /dev/null +++ b/src/linker.web/public/upnp-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/linker.web/public/upnp-yellow.svg b/src/linker.web/public/upnp-yellow.svg new file mode 100644 index 00000000..84b73e70 --- /dev/null +++ b/src/linker.web/public/upnp-yellow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/linker.web/public/upnp.svg b/src/linker.web/public/upnp.svg new file mode 100644 index 00000000..d34b36c8 --- /dev/null +++ b/src/linker.web/public/upnp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/linker.web/src/apis/tunnel.js b/src/linker.web/src/apis/tunnel.js index a15afd3f..1b7c768b 100644 --- a/src/linker.web/src/apis/tunnel.js +++ b/src/linker.web/src/apis/tunnel.js @@ -1,5 +1,17 @@ import { sendWebsocketMsg } from './request' +export const getUpnpMappingInfo = (machineid) => { + return sendWebsocketMsg('tunnel/getmapping',machineid); +} +export const getUpnpMappingLocalInfo = (machineid) => { + return sendWebsocketMsg('tunnel/getmappinglocal',machineid); +} +export const addUpnpMappingInfo = (machineid,value) => { + return sendWebsocketMsg('tunnel/addmapping',{Key:machineid,Value:value}); +} +export const delUpnpMappingInfo = (machineid,port,protocolType) => { + return sendWebsocketMsg('tunnel/delmapping',{Key:machineid,Value:{Key:port,Value:protocolType}}); +} export const getTunnelInfo = (hashcode = '0') => { return sendWebsocketMsg('tunnel/get', hashcode); } diff --git a/src/linker.web/src/lang/en-us.js b/src/linker.web/src/lang/en-us.js index b6edc8f7..50d1f34e 100644 --- a/src/linker.web/src/lang/en-us.js +++ b/src/linker.web/src/lang/en-us.js @@ -12,6 +12,7 @@ export default { 'common.p2p': 'P2P', 'common.refresh': 'Refresh', 'common.copied': 'Copied', + 'common.deleteText': 'Are you sure delete {0}?', 'head.home': 'Home', 'head.server': 'Server', diff --git a/src/linker.web/src/lang/zh-cn.js b/src/linker.web/src/lang/zh-cn.js index a4790794..63461869 100644 --- a/src/linker.web/src/lang/zh-cn.js +++ b/src/linker.web/src/lang/zh-cn.js @@ -12,6 +12,7 @@ export default { 'common.p2p': '打洞', 'common.refresh': '刷新', 'common.copied': '已复制', + 'common.deleteText': '确认删除{0}吗?', 'head.home': '首页', 'head.server': '服务器', diff --git a/src/linker.web/src/views/components/device/devices.js b/src/linker.web/src/views/components/device/devices.js index deb0fbc1..9869d273 100644 --- a/src/linker.web/src/views/components/device/devices.js +++ b/src/linker.web/src/views/components/device/devices.js @@ -75,7 +75,7 @@ export const provideDevices = () => { } } - await Promise.all(Object.values(hooks).map(hook=>dataFn(hook))); + Object.values(hooks).forEach(hook=>dataFn(hook)) devices.timer1 = setTimeout(fn,1000); } diff --git a/src/linker.web/src/views/components/tunnel/Tunnel.vue b/src/linker.web/src/views/components/tunnel/Tunnel.vue index 580550a5..7d49c677 100644 --- a/src/linker.web/src/views/components/tunnel/Tunnel.vue +++ b/src/linker.web/src/views/components/tunnel/Tunnel.vue @@ -7,21 +7,16 @@ @@ -87,6 +95,8 @@ export default { 'aliyun':'aliyun.svg', 'alibaba':'aliyun.svg', 'jdcom':'jdcom.svg', + 'jdcom':'jdcom.svg', + 'cloudflare':'cloudflare.svg' } const regex = new RegExp(Object.keys(imgMap).map(item => `\\b${item}\\b`).join("|")); const netImg = (item)=>{ @@ -127,8 +137,25 @@ export default { tunnel.value.current = _tunnel; tunnel.value.showEdit = true; } + const handleUpnp = (_tunnel,row,access)=>{ + if(machineId.value === _tunnel.MachineId){ + if(!access.TunnelChangeSelf){ + ElMessage.success(t('common.access')); + return; + } + }else{ + if(!access.TunnelChangeOther){ + ElMessage.success(t('common.access')); + return; + } + } + _tunnel.device = row; + tunnel.value.current = _tunnel; + tunnel.value.showUpnp = true; + } + return { - handleTunnel,netImg,natMap + handleTunnel,handleUpnp,netImg,natMap } } } diff --git a/src/linker.web/src/views/components/tunnel/TunnelUpnp.vue b/src/linker.web/src/views/components/tunnel/TunnelUpnp.vue new file mode 100644 index 00000000..021f3b3d --- /dev/null +++ b/src/linker.web/src/views/components/tunnel/TunnelUpnp.vue @@ -0,0 +1,50 @@ + + + \ No newline at end of file diff --git a/src/linker.web/src/views/components/tunnel/TunnelUpnpLocal.vue b/src/linker.web/src/views/components/tunnel/TunnelUpnpLocal.vue new file mode 100644 index 00000000..9c5d76a2 --- /dev/null +++ b/src/linker.web/src/views/components/tunnel/TunnelUpnpLocal.vue @@ -0,0 +1,150 @@ + + + \ No newline at end of file diff --git a/src/linker.web/src/views/components/tunnel/TunnelUpnpLocalAdd.vue b/src/linker.web/src/views/components/tunnel/TunnelUpnpLocalAdd.vue new file mode 100644 index 00000000..8f048cb7 --- /dev/null +++ b/src/linker.web/src/views/components/tunnel/TunnelUpnpLocalAdd.vue @@ -0,0 +1,125 @@ + + + \ No newline at end of file diff --git a/src/linker.web/src/views/components/tunnel/TunnelUpnpRemote.vue b/src/linker.web/src/views/components/tunnel/TunnelUpnpRemote.vue new file mode 100644 index 00000000..aa36219a --- /dev/null +++ b/src/linker.web/src/views/components/tunnel/TunnelUpnpRemote.vue @@ -0,0 +1,137 @@ + + + \ No newline at end of file diff --git a/src/linker.web/src/views/components/tunnel/tunnel.js b/src/linker.web/src/views/components/tunnel/tunnel.js index 8abf48e4..b855347d 100644 --- a/src/linker.web/src/views/components/tunnel/tunnel.js +++ b/src/linker.web/src/views/components/tunnel/tunnel.js @@ -11,6 +11,7 @@ export const provideTunnel = () => { hashcode1: 0, showEdit: false, + showUpnp: false, current: null, }); provide(tunnelSymbol, tunnel); diff --git a/src/linker.web/src/views/layout/full/list/Index.vue b/src/linker.web/src/views/layout/full/list/Index.vue index 803f7df6..fdfb3943 100644 --- a/src/linker.web/src/views/layout/full/list/Index.vue +++ b/src/linker.web/src/views/layout/full/list/Index.vue @@ -22,6 +22,8 @@ + + @@ -58,6 +60,7 @@ import { provideSocks5 } from '../../../components/socks5/socks5' import Tunnel from '../../../components/tunnel/Tunnel.vue' import TunnelEdit from '../../../components/tunnel/TunnelEdit.vue' +import TunnelUpnp from '../../../components/tunnel/TunnelUpnp.vue' import { provideTunnel } from '../../../components/tunnel/tunnel' import { provideUpdater } from '../../../components/updater/updater' @@ -105,7 +108,7 @@ export default { AccessEdit, Tunnel,TunnelEdit, ConnectionsEdit, - Tuntap,TuntapEdit,TuntapLease, + Tuntap,TuntapEdit,TunnelUpnp,TuntapLease, Socks5, Socks5Edit, Forward,ForwardEdit, SForwardEdit ,UpdaterConfirm, diff --git a/src/linker/linker.csproj b/src/linker/linker.csproj index 90175e53..9245d3ce 100644 --- a/src/linker/linker.csproj +++ b/src/linker/linker.csproj @@ -20,7 +20,8 @@ 3. #89 windows下利用任务计划进行进程守护,定时检查服务 4. 优化客户端管理ws并发 5. 减少服务器推送,减轻主服务器压力 -6. 优化中继,针对无法连接自己的外网IP的情况 +6. 优化中继,针对无法连接自己的外网IP的情况 +7. 优化upnp,自己实现了一遍 snltty https://github.com/snltty/linker https://github.com/snltty/linker