mirror of
https://github.com/snltty/linker.git
synced 2026-04-22 15:27:06 +08:00
upnp和nat-pmp
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
@@ -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
|
||||
|
||||
+3
-2
@@ -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的情况
|
||||
6. 优化中继,针对无法连接自己的外网IP的情况
|
||||
7. 优化upnp,自己实现了一遍
|
||||
@@ -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
|
||||
|
||||
@@ -42,6 +42,27 @@ namespace linker.libs
|
||||
|
||||
public static partial class NetworkHelper
|
||||
{
|
||||
private static readonly HashSet<System.Net.IPNetwork> privateNetworks = new HashSet<System.Net.IPNetwork>
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<byte> 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))
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<PortMappingInfo>
|
||||
{
|
||||
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> 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<IPAddress>();
|
||||
value.PublicPort = reader.ReadValue<int>();
|
||||
value.PrivatePort = reader.ReadValue<int>();
|
||||
value.ProtocolType = reader.ReadValue<ProtocolType>();
|
||||
value.Enabled = reader.ReadValue<bool>();
|
||||
value.Description = reader.ReadValue<string>();
|
||||
value.LeaseDuration = reader.ReadValue<int>();
|
||||
value.DeviceType = reader.ReadValue<DeviceType>();
|
||||
value.Deletable = reader.ReadValue<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using linker.libs;
|
||||
using linker.messenger.tunnel;
|
||||
using linker.messenger.tunnel.client;
|
||||
using linker.tunnel.transport;
|
||||
using System.Net;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using linker.messenger.tuntap;
|
||||
using linker.messenger.tuntap.client;
|
||||
|
||||
namespace linker.messenger.store.file.tuntap
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, PortMappingInfo> info = serializer.Deserialize<KeyValuePair<string, PortMappingInfo>>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
_ = tunnelNetworkTransfer.AddMapping(info.Value).ConfigureAwait(false);
|
||||
}
|
||||
[MessengerId((ushort)TunnelMessengerIds.UpnpDel)]
|
||||
public async Task UpnpDel(IConnection connection)
|
||||
{
|
||||
KeyValuePair<string, KeyValuePair<int, ProtocolType>> info = serializer.Deserialize<KeyValuePair<string, KeyValuePair<int, ProtocolType>>>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
_ = tunnelNetworkTransfer.DelMapping(info.Value.Key, info.Value.Value).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -321,5 +351,103 @@ namespace linker.messenger.tunnel
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MessengerId((ushort)TunnelMessengerIds.UpnpGetForward)]
|
||||
public void UpnpGetForward(IConnection connection)
|
||||
{
|
||||
string machineid = serializer.Deserialize<string>(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<string>(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<string, PortMappingInfo> info = serializer.Deserialize<KeyValuePair<string, PortMappingInfo>>(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<string, KeyValuePair<int, ProtocolType>> info = serializer.Deserialize<KeyValuePair<string, KeyValuePair<int, ProtocolType>>>(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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using linker.tunnel.transport;
|
||||
|
||||
namespace linker.messenger.tunnel
|
||||
namespace linker.messenger.tunnel.client
|
||||
{
|
||||
/// <summary>
|
||||
/// 打洞排除IP
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+84
-9
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理接口
|
||||
@@ -38,6 +40,80 @@ namespace linker.messenger.tunnel
|
||||
this.tunnelNetworkTransfer = tunnelNetworkTransfer;
|
||||
this.tunnelTransfer = tunnelTransfer;
|
||||
this.tunnelMessengerAdapter = tunnelMessengerAdapter;
|
||||
|
||||
}
|
||||
|
||||
public async Task<List<PortMappingInfo>> 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<List<PortMappingInfo>>(resp.Data.Span);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
public async Task<List<PortMappingInfo>> 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<List<PortMappingInfo>>(resp.Data.Span);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
public async Task<bool> AddMapping(ApiControllerParamsInfo param)
|
||||
{
|
||||
KeyValueInfo<string, PortMappingInfo> info = param.Content.DeJson<KeyValueInfo<string, PortMappingInfo>>();
|
||||
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<string, PortMappingInfo>(info.Key, info.Value))
|
||||
}).ConfigureAwait(false);
|
||||
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
|
||||
}
|
||||
public async Task<bool> DelMapping(ApiControllerParamsInfo param)
|
||||
{
|
||||
KeyValueInfo<string, KeyValueInfo<int, ProtocolType>> info = param.Content.DeJson<KeyValueInfo<string, KeyValueInfo<int, ProtocolType>>>();
|
||||
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<string, KeyValuePair<int, ProtocolType>>(info.Key, new KeyValuePair<int, ProtocolType>(info.Value.Key, info.Value.Value)))
|
||||
}).ConfigureAwait(false);
|
||||
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,7 +252,6 @@ namespace linker.messenger.tunnel
|
||||
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
|
||||
}
|
||||
|
||||
|
||||
public async Task<TunnelLocalNetworkInfo> GetNetwork(ApiControllerParamsInfo param)
|
||||
{
|
||||
if (param.Content == signInClientStore.Id)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using linker.tunnel.transport;
|
||||
|
||||
namespace linker.messenger.tunnel
|
||||
namespace linker.messenger.tunnel.client
|
||||
{
|
||||
/// <summary>
|
||||
/// 打洞排除IP
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 打洞信标适配存储
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+2
-3
@@ -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
|
||||
{
|
||||
+35
-6
@@ -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<PortMappingInfo> GetMapping()
|
||||
{
|
||||
return PortMappingUtility.Get();
|
||||
}
|
||||
public List<PortMappingInfo> 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);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 刷新网关等级数据
|
||||
/// </summary>
|
||||
@@ -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()
|
||||
{
|
||||
+2
-4
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@ using linker.libs.extends;
|
||||
using linker.libs;
|
||||
using System.Text;
|
||||
|
||||
namespace linker.messenger.tunnel
|
||||
namespace linker.messenger.tunnel.server
|
||||
{
|
||||
/// <summary>
|
||||
/// 外网端口处理器
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using linker.libs;
|
||||
using linker.messenger.tuntap.client;
|
||||
using System.Net;
|
||||
|
||||
namespace linker.messenger.tuntap.cidr
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace linker.messenger.tuntap
|
||||
namespace linker.messenger.tuntap.client
|
||||
{
|
||||
public interface ITuntapClientStore
|
||||
{
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace linker.messenger.tuntap
|
||||
namespace linker.messenger.tuntap.client
|
||||
{
|
||||
public interface ITuntapSystemInformation
|
||||
{
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
UpdateServer170 = 2617,
|
||||
|
||||
UpdateServer184 = 2619,
|
||||
UpdateServer186 = 2020,
|
||||
UpdateServer186 = 2620,
|
||||
|
||||
Max = 2299
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public sealed class TunnelUpnpTransfer
|
||||
{
|
||||
|
||||
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
|
||||
private readonly ConcurrentDictionary<NatProtocol, INatDevice> natDevices = new ConcurrentDictionary<NatProtocol, INatDevice>();
|
||||
|
||||
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<bool> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置端口映射,内网端口和外网端口不一样
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.4" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\linker.libs\linker.libs.csproj" />
|
||||
<ProjectReference Include="..\linker.upnp\linker.upnp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using linker.libs;
|
||||
using Mono.Nat;
|
||||
using System.Net;
|
||||
|
||||
namespace linker.tunnel.wanport
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 内网ip
|
||||
/// </summary>
|
||||
public IPAddress ClientIp { get; set; }
|
||||
/// <summary>
|
||||
/// 外网端口
|
||||
/// </summary>
|
||||
public int PublicPort { get; set; }
|
||||
/// <summary>
|
||||
/// 内网端口
|
||||
/// </summary>
|
||||
public int PrivatePort { get; set; }
|
||||
/// <summary>
|
||||
/// 协议
|
||||
/// </summary>
|
||||
public ProtocolType ProtocolType { get; set; }
|
||||
/// <summary>
|
||||
/// 启用
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// 存活
|
||||
/// </summary>
|
||||
public int LeaseDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 类型
|
||||
/// </summary>
|
||||
public DeviceType Type { get; }
|
||||
public IPAddress GatewayIp { get; set; }
|
||||
public IPAddress WanIp { get; set; }
|
||||
/// <summary>
|
||||
/// 获取设备所有映射
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<List<PortMappingInfo>> Get();
|
||||
/// <summary>
|
||||
/// 添加映射
|
||||
/// </summary>
|
||||
/// <param name="mapping"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> Add(PortMappingInfo mapping);
|
||||
/// <summary>
|
||||
/// 删除映射
|
||||
/// </summary>
|
||||
/// <param name="publicPort"></param>
|
||||
/// <param name="protocol"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> Delete(int publicPort, ProtocolType protocol);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace linker.upnp
|
||||
{
|
||||
public interface IPortMappingService
|
||||
{
|
||||
public DeviceType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 发现
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public Task<List<IPortMappingDevice>> Discovery(CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// 获取本类型的所有已发现的设备
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<IPortMappingDevice> GetDevices();
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有设备的所有映射信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<List<PortMappingInfo>> Get();
|
||||
/// <summary>
|
||||
/// 添加一条映射
|
||||
/// </summary>
|
||||
/// <param name="mapping"></param>
|
||||
/// <returns></returns>
|
||||
public Task Add(PortMappingInfo mapping);
|
||||
/// <summary>
|
||||
/// 删除一条映射
|
||||
/// </summary>
|
||||
/// <param name="publicPort"></param>
|
||||
/// <param name="protocol"></param>
|
||||
/// <returns></returns>
|
||||
public Task Delete(int publicPort, ProtocolType protocol);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<List<PortMappingInfo>> Get()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
public async Task<bool> 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<bool> 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<string, IPortMappingDevice> pmpDevices = new();
|
||||
|
||||
public async Task<List<IPortMappingDevice>> Discovery(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Task<PmpDevice>> 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<IPAddress> 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<IPortMappingDevice> GetDevices()
|
||||
{
|
||||
return pmpDevices.Values.ToList<IPortMappingDevice>();
|
||||
}
|
||||
|
||||
public async Task<List<PortMappingInfo>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备类型
|
||||
/// </summary>
|
||||
public DeviceType Type => DeviceType.Upnp;
|
||||
public IPAddress GatewayIp { get; set; }
|
||||
public IPAddress WanIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务类型描述
|
||||
/// </summary>
|
||||
public string ServiceType { get; set; }
|
||||
/// <summary>
|
||||
/// 控制地址
|
||||
/// </summary>
|
||||
public string ControlUrl { get; set; }
|
||||
/// <summary>
|
||||
/// 内网ip
|
||||
/// </summary>
|
||||
public IPAddress ClientIp { get; set; }
|
||||
public async Task<List<PortMappingInfo>> Get()
|
||||
{
|
||||
List<PortMappingInfo> result = new List<PortMappingInfo>();
|
||||
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<bool> 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<bool> 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 $@"<?xml version=""1.0""?>
|
||||
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/""
|
||||
s:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
|
||||
<s:Body>
|
||||
<u:AddPortMapping xmlns:u=""{ServiceType}"">
|
||||
<NewRemoteHost></NewRemoteHost>
|
||||
<NewExternalPort>{mapping.PublicPort}</NewExternalPort>
|
||||
<NewProtocol>{mapping.ProtocolType.ToString().ToUpper()}</NewProtocol>
|
||||
<NewInternalPort>{mapping.PrivatePort}</NewInternalPort>
|
||||
<NewInternalClient>{mapping.ClientIp}</NewInternalClient>
|
||||
<NewEnabled>{(mapping.Enabled ? "1" : "0")}</NewEnabled>
|
||||
<NewPortMappingDescription>{mapping.Description}</NewPortMappingDescription>
|
||||
<NewLeaseDuration>{mapping.LeaseDuration}</NewLeaseDuration>
|
||||
</u:AddPortMapping>
|
||||
</s:Body>
|
||||
</s:Envelope> ";
|
||||
}
|
||||
private string BuildDeletePortMappingRequest(int publicPort, ProtocolType protocol)
|
||||
{
|
||||
return $@"<?xml version=""1.0""?>
|
||||
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/""
|
||||
s:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
|
||||
<s:Body>
|
||||
<u:DeletePortMapping xmlns:u=""{ServiceType}"">
|
||||
<NewRemoteHost></NewRemoteHost>
|
||||
<NewExternalPort>{publicPort}</NewExternalPort>
|
||||
<NewProtocol>{protocol.ToString().ToUpper()}</NewProtocol>
|
||||
</u:DeletePortMapping>
|
||||
</s:Body>
|
||||
</s:Envelope>";
|
||||
}
|
||||
private string BuildGetPortMappingRequest(int index)
|
||||
{
|
||||
return $@"<?xml version=""1.0""?>
|
||||
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/""
|
||||
s:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">
|
||||
<s:Body>
|
||||
<u:GetGenericPortMappingEntry xmlns:u=""{ServiceType}"">
|
||||
<NewPortMappingIndex>{index}</NewPortMappingIndex>
|
||||
</u:GetGenericPortMappingEntry>
|
||||
</s:Body>
|
||||
</s:Envelope>";
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"类型:UPNP->客户端ip:{ClientIp}->控制地址:{ControlUrl}->服务描述:{ServiceType}";
|
||||
}
|
||||
|
||||
}
|
||||
public sealed class PortMappingUpnpService : IPortMappingService
|
||||
{
|
||||
public DeviceType Type => DeviceType.Upnp;
|
||||
|
||||
|
||||
private readonly ConcurrentDictionary<string, IPortMappingDevice> 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<List<IPortMappingDevice>> Discovery(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Task<(string response, IPAddress ip, IPAddress gateway)>> 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<IPortMappingDevice>();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task<UpnpDevice> 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<IPAddress> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已找到的upnp设备
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<IPortMappingDevice> GetDevices()
|
||||
{
|
||||
return upnpDevices.Values.ToList<IPortMappingDevice>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有upnp设备的所有映射信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<List<PortMappingInfo>> Get()
|
||||
{
|
||||
return (await Task.WhenAll(upnpDevices.Values.Select(c => c.Get()).ToList()).ConfigureAwait(false)).SelectMany(c => c).ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加一条映射
|
||||
/// </summary>
|
||||
/// <param name="mapping"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Add(PortMappingInfo mapping)
|
||||
{
|
||||
foreach (var device in upnpDevices.Values)
|
||||
{
|
||||
await device.Add(mapping).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 删除一条映射
|
||||
/// </summary>
|
||||
/// <param name="publicPort"></param>
|
||||
/// <param name="protocol"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Delete(int publicPort, ProtocolType protocol)
|
||||
{
|
||||
foreach (var device in upnpDevices.Values)
|
||||
{
|
||||
await device.Delete(publicPort, protocol).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace linker.upnp
|
||||
{
|
||||
public static class PortMappingUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 当发现设备
|
||||
/// </summary>
|
||||
public static event Action<IPortMappingDevice> OnDeviceFound
|
||||
{
|
||||
add => DeviceFound += value;
|
||||
remove => DeviceFound -= value;
|
||||
}
|
||||
private static Action<IPortMappingDevice> DeviceFound = (device) => { };
|
||||
|
||||
/// <summary>
|
||||
/// 当发生变化,发现设备,添加删除映射什么的
|
||||
/// </summary>
|
||||
public static event Action OnChange
|
||||
{
|
||||
add => Change += value;
|
||||
remove => Change -= value;
|
||||
}
|
||||
private static Action Change = () => { };
|
||||
|
||||
/// <summary>
|
||||
/// 设备数
|
||||
/// </summary>
|
||||
public static int DeviceCount => devices.Count;
|
||||
/// <summary>
|
||||
/// 映射数
|
||||
/// </summary>
|
||||
public static int MappingCount => mappings.Count;
|
||||
/// <summary>
|
||||
/// 本地数
|
||||
/// </summary>
|
||||
public static int LocalMappingCount => localMappings.Where(c => c.Value.Deleted == false).Count();
|
||||
/// <summary>
|
||||
/// 外网数量
|
||||
/// </summary>
|
||||
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<PortMappingInfo> mappings = [];
|
||||
|
||||
private static CancellationTokenSource cts;
|
||||
private static CancellationTokenSource _cts;
|
||||
private static long ticks = Environment.TickCount64;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开始发现设备
|
||||
/// </summary>
|
||||
/// <param name="deviceType"></param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止发现设备
|
||||
/// </summary>
|
||||
public static void StopDiscovery()
|
||||
{
|
||||
cts?.Cancel();
|
||||
_cts?.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已发现设备
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<IPortMappingDevice> GetDevices()
|
||||
{
|
||||
RefreshDelay();
|
||||
return services.Select(c => c.GetDevices()).ToList().SelectMany(c => c).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已发现设备的所有映射信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<PortMappingInfo> Get()
|
||||
{
|
||||
RefreshDelay();
|
||||
return mappings
|
||||
.Where(c => localMappings.TryGetValue((c.PublicPort, c.ProtocolType), out MappingCacheInfo cache) == false || cache.Deleted == false)
|
||||
.ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取本地添加的映射信息(不包含已删除的)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static List<PortMappingInfo> GetLocal()
|
||||
{
|
||||
RefreshDelay();
|
||||
return localMappings.Values.Where(c => c.Deleted == false).Select(c => c.Info).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一条映射
|
||||
/// </summary>
|
||||
/// <param name="mapping"></param>
|
||||
/// <returns></returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除一条映射
|
||||
/// </summary>
|
||||
/// <param name="publicPort"></param>
|
||||
/// <param name="protocol"></param>
|
||||
/// <returns></returns>
|
||||
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<IPAddress> Added { get; set; } = new HashSet<IPAddress>();
|
||||
}
|
||||
|
||||
|
||||
private static readonly HashSet<IPNetwork> privateNetworks = new HashSet<IPNetwork>
|
||||
{
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PublishAot>false</PublishAot>
|
||||
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
|
||||
<Title>linker upnp</Title>
|
||||
<Authors>snltty</Authors>
|
||||
<Company>snltty</Company>
|
||||
<Description>linker upnp</Description>
|
||||
<Copyright>snltty</Copyright>
|
||||
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
|
||||
<PackageReleaseNotes>linker upnp</PackageReleaseNotes>
|
||||
<Version>1.9.9</Version>
|
||||
<AssemblyVersion>1.9.9</AssemblyVersion>
|
||||
<FileVersion>1.9.9</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772524795472" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5280" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M704.38432 718.72c6.304-21.632 3.872-41.408-6.624-56.128-9.568-13.504-25.792-21.28-45.28-22.208l-369.472-4.8h-0.096a6.688 6.688 0 0 1-5.568-3.008l-0.032-0.032a8.192 8.192 0 0 1-0.896-6.624 10.24 10.24 0 0 1 8.672-6.656l372.736-4.8c44.16-2.08 92.16-37.824 108.96-81.632l21.28-55.52a11.584 11.584 0 0 0 0.64-7.168 242.4 242.4 0 0 0-236.8-189.664 242.656 242.656 0 0 0-229.856 164.768 110.176 110.176 0 0 0-76.544-21.28 109.28 109.28 0 0 0-94.816 135.648 155.04 155.04 0 0 0-150.656 155.168c0 7.456 0.608 15.008 1.504 22.496a7.456 7.456 0 0 0 7.2 6.304h681.888a9.312 9.312 0 0 0 8.672-6.624z m117.6-237.376c-3.296 0-6.88 0-10.176 0.48-2.4 0-4.512 1.76-5.408 4.16l-14.4 50.112c-6.304 21.632-3.904 41.408 6.592 56.16 9.632 13.504 25.824 21.248 45.344 22.176l78.656 4.832c2.368 0 4.512 1.12 5.664 3.008a8.64 8.64 0 0 1 0.928 6.656 10.24 10.24 0 0 1-8.704 6.624l-81.952 4.8c-44.416 2.08-92.096 37.824-108.928 81.664l-5.984 15.296c-1.216 3.04 0.928 6.048 4.192 6.048h281.504a7.36 7.36 0 0 0 7.2-5.376c4.8-17.408 7.488-35.712 7.488-54.624 0-111.04-90.656-201.696-202.016-201.696z" fill="#F38020" p-id="5281"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772610299623" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14439" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M789.333333 264.533333l-217.6-115.2c-38.4-21.333333-81.066667-21.333333-119.466666 0L234.666667 264.533333c-42.666667 21.333333-68.266667 64-68.266667 110.933334v226.133333c0 46.933333 25.6 89.6 68.266667 110.933333l217.6 115.2c38.4 21.333333 81.066667 21.333333 119.466666 0l217.6-115.2c42.666667-21.333333 68.266667-64 68.266667-110.933333V375.466667c0-46.933333-25.6-89.6-68.266667-110.933334z m-298.666666-38.4c12.8-8.533333 25.6-8.533333 38.4 0l187.733333 102.4L512 422.4 311.466667 320l179.2-93.866667zM469.333333 746.666667L277.333333 640c-12.8-8.533333-21.333333-21.333333-21.333333-38.4v-213.333333l217.6 106.666666v251.733334z m29.866667 12.8h21.333333c-4.266667 4.266667-12.8 4.266667-21.333333 0z m273.066667-153.6c0 17.066667-8.533333 29.866667-21.333334 38.4L554.666667 746.666667v-247.466667l217.6-98.133333v204.8z" fill="#008000" p-id="14440"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772610299623" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14439" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M789.333333 264.533333l-217.6-115.2c-38.4-21.333333-81.066667-21.333333-119.466666 0L234.666667 264.533333c-42.666667 21.333333-68.266667 64-68.266667 110.933334v226.133333c0 46.933333 25.6 89.6 68.266667 110.933333l217.6 115.2c38.4 21.333333 81.066667 21.333333 119.466666 0l217.6-115.2c42.666667-21.333333 68.266667-64 68.266667-110.933333V375.466667c0-46.933333-25.6-89.6-68.266667-110.933334z m-298.666666-38.4c12.8-8.533333 25.6-8.533333 38.4 0l187.733333 102.4L512 422.4 311.466667 320l179.2-93.866667zM469.333333 746.666667L277.333333 640c-12.8-8.533333-21.333333-21.333333-21.333333-38.4v-213.333333l217.6 106.666666v251.733334z m29.866667 12.8h21.333333c-4.266667 4.266667-12.8 4.266667-21.333333 0z m273.066667-153.6c0 17.066667-8.533333 29.866667-21.333334 38.4L554.666667 746.666667v-247.466667l217.6-98.133333v204.8z" fill="#da790d" p-id="14440"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772610299623" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14439" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M789.333333 264.533333l-217.6-115.2c-38.4-21.333333-81.066667-21.333333-119.466666 0L234.666667 264.533333c-42.666667 21.333333-68.266667 64-68.266667 110.933334v226.133333c0 46.933333 25.6 89.6 68.266667 110.933333l217.6 115.2c38.4 21.333333 81.066667 21.333333 119.466666 0l217.6-115.2c42.666667-21.333333 68.266667-64 68.266667-110.933333V375.466667c0-46.933333-25.6-89.6-68.266667-110.933334z m-298.666666-38.4c12.8-8.533333 25.6-8.533333 38.4 0l187.733333 102.4L512 422.4 311.466667 320l179.2-93.866667zM469.333333 746.666667L277.333333 640c-12.8-8.533333-21.333333-21.333333-21.333333-38.4v-213.333333l217.6 106.666666v251.733334z m29.866667 12.8h21.333333c-4.266667 4.266667-12.8 4.266667-21.333333 0z m273.066667-153.6c0 17.066667-8.533333 29.866667-21.333334 38.4L554.666667 746.666667v-247.466667l217.6-98.133333v204.8z" fill="#008000" p-id="14440"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -12,6 +12,7 @@ export default {
|
||||
'common.p2p': '打洞',
|
||||
'common.refresh': '刷新',
|
||||
'common.copied': '已复制',
|
||||
'common.deleteText': '确认删除{0}吗?',
|
||||
|
||||
'head.home': '首页',
|
||||
'head.server': '服务器',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -7,21 +7,16 @@
|
||||
<div class="skeleton-animation" :style="`animation-delay:${scope.row.animationDelay}ms`">
|
||||
<div>
|
||||
<template v-if="scope.row.hook_tunnel.Net.CountryCode">
|
||||
<img
|
||||
:title="`${scope.row.hook_tunnel.Net.CountryCode}、${scope.row.hook_tunnel.Net.City}`"
|
||||
class="system"
|
||||
:src="`https://unpkg.com/flag-icons@7.2.3/flags/4x3/${scope.row.hook_tunnel.Net.CountryCode.toLowerCase()}.svg`" />
|
||||
<img class="system" :title="`${scope.row.hook_tunnel.Net.CountryCode}、${scope.row.hook_tunnel.Net.City}`" :src="`https://unpkg.com/flag-icons@7.2.3/flags/4x3/${scope.row.hook_tunnel.Net.CountryCode.toLowerCase()}.svg`" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<img title="?" class="system" src="/system.svg" />
|
||||
</template>
|
||||
<template v-if="scope.row.hook_tunnel.Net.Isp">
|
||||
<img
|
||||
:title="`${scope.row.hook_tunnel.Net.Isp}`"
|
||||
class="system" :src="netImg(scope.row.hook_tunnel.Net)" />
|
||||
<img class="system" :title="`${scope.row.hook_tunnel.Net.Isp}`" :src="netImg(scope.row.hook_tunnel.Net)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<img title="?" class="system" src="/system.svg" />
|
||||
<img class="system" title="?" src="/system.svg" />
|
||||
</template>
|
||||
<template v-if="scope.row.hook_tunnel.Net.Nat">
|
||||
<span class="nat" :title="scope.row.hook_tunnel.Net.Nat">{{ natMap[scope.row.hook_tunnel.Net.Nat] }}</span>
|
||||
@@ -31,11 +26,24 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<a href="javascript:;" class="a-line" title="upnp,绿色可用,黄色大概率可用,灰色几乎不可用"
|
||||
@click="handleUpnp(scope.row.hook_tunnel,scope.row,values)"
|
||||
:class="scope.row.hook_counter['upnp-w'] > 0 && scope.row.hook_counter['upnp-d'] > 0
|
||||
? 'green' : (scope.row.hook_counter['upnp-w']> 0 || scope.row.hook_counter['upnp-d'] > 0
|
||||
? 'yellow' : '')">
|
||||
|
||||
<img title="upnp" class="system"
|
||||
:src="scope.row.hook_counter['upnp-w'] > 0 && scope.row.hook_counter['upnp-d'] > 0
|
||||
? '/upnp-green.svg' : (scope.row.hook_counter['upnp-w']> 0 || scope.row.hook_counter['upnp-d'] > 0
|
||||
? '/upnp-yellow.svg' : '/upnp.svg')"/>
|
||||
<span>{{ scope.row.hook_counter['upnp-l'] }}-{{ scope.row.hook_counter['upnp-r'] }}</span>
|
||||
</a>
|
||||
<span class="flex-1"></span>
|
||||
<a href="javascript:;" class="a-line"
|
||||
:class="{yellow:scope.row.hook_tunnel.NeedReboot}"
|
||||
:title="$t('home.holeText')"
|
||||
@click="handleTunnel(scope.row.hook_tunnel,scope.row,values)">
|
||||
<span>{{$t('home.jump')}}:{{scope.row.hook_tunnel.RouteLevel}}+{{scope.row.hook_tunnel.RouteLevelPlus}}</span>
|
||||
:class="{yellow:scope.row.hook_tunnel.NeedReboot}"
|
||||
:title="$t('home.holeText')"
|
||||
@click="handleTunnel(scope.row.hook_tunnel,scope.row,values)">
|
||||
{{scope.row.hook_tunnel.RouteLevel}}+{{scope.row.hook_tunnel.RouteLevelPlus}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-dialog v-model="state.show" :close-on-click-modal="false" append-to=".app-wrap" :title="`设置[${state.machineName}]UPNP`" width="98%" top="2vh">
|
||||
<div>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="我加的">
|
||||
<TunnelUpnpLocal :deviceTypes="state.deviceTypes" :protocolTypes="state.protocolTypes" :machineId="state.machineId"></TunnelUpnpLocal>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="网关里的">
|
||||
<TunnelUpnpRemote :deviceTypes="state.deviceTypes" :protocolTypes="state.protocolTypes" :machineId="state.machineId"></TunnelUpnpRemote>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import {reactive, watch } from 'vue';
|
||||
import {useTunnel } from './tunnel';
|
||||
import TunnelUpnpLocal from './TunnelUpnpLocal.vue';
|
||||
import TunnelUpnpRemote from './TunnelUpnpRemote.vue';
|
||||
export default {
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
components: {TunnelUpnpLocal,TunnelUpnpRemote},
|
||||
setup(props, { emit }) {
|
||||
|
||||
const tunnel = useTunnel();
|
||||
const state = reactive({
|
||||
tab: '',
|
||||
show: true,
|
||||
machineName: tunnel.value.current.device.MachineName,
|
||||
machineId: tunnel.value.current.device.MachineId,
|
||||
deviceTypes:{1:'UPNP',2:'NAT-PMP',4:'PCP',255:'Any'},
|
||||
protocolTypes:{6:'TCP',17:'UDP'},
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="head pdb-6 t-c">
|
||||
<el-input size="small" v-model="state.name" @change="handleSearch" clearable style="width:10rem;margin-right:1rem"></el-input>
|
||||
<el-button size="small" :loading="state.loading" @click="handleSearch"><el-icon><Search></Search></el-icon> </el-button>
|
||||
<el-button size="small" type="primary" :loading="state.loading" @click="handleAdd">添加</el-button>
|
||||
</div>
|
||||
<el-table stripe :data="state.list" border size="small" width="100%" height="60vh">
|
||||
<el-table-column prop="DeviceType" label="类型" width="66" sortable>
|
||||
<template #default="scope">{{ deviceTypes[scope.row.DeviceType] }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="PublicPort" label="外网端口" width="90" sortable></el-table-column>
|
||||
<el-table-column prop="ClientIp" label="内网ip" width="100" sortable></el-table-column>
|
||||
<el-table-column prop="PrivatePort" label="内网端口" width="90" sortable></el-table-column>
|
||||
<el-table-column prop="ProtocolType" label="协议" width="66" sortable>
|
||||
<template #default="scope">{{ protocolTypes[scope.row.ProtocolType] }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="LeaseDuration" label="存活" width="66" sortable></el-table-column>
|
||||
<el-table-column property="Disabled" label="启用" width="66" sortable>
|
||||
<template #default="scope">
|
||||
<el-switch :disabled="scope.row.Deletable==false" v-model="scope.row.Enabled" @change="handleEnabledChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="Description" label="描述" width="200"></el-table-column>
|
||||
|
||||
<el-table-column property="Oper" label="" width="60" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="danger" v-if="scope.row.Deletable" @click="handleDel(scope.row)"><el-icon><DeleteFilled></DeleteFilled></el-icon> </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<TunnelUpnpLocalAdd v-if="state.showAdd" v-model="state.showAdd"
|
||||
:deviceTypes="deviceTypes" :protocolTypes="protocolTypes" :machineId="machineId" @change="handleSearch"></TunnelUpnpLocalAdd>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { onMounted, onUnmounted, provide, reactive, ref } from 'vue';
|
||||
import { addUpnpMappingInfo, delUpnpMappingInfo, getUpnpMappingLocalInfo } from '@/apis/tunnel';
|
||||
import {DeleteFilled,Search} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import TunnelUpnpLocalAdd from './TunnelUpnpLocalAdd.vue';
|
||||
|
||||
export default {
|
||||
props: ['modelValue','deviceTypes','protocolTypes','machineId'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { DeleteFilled,Search,TunnelUpnpLocalAdd },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const { t } = useI18n();
|
||||
const state = reactive({
|
||||
name: '',
|
||||
list: [],
|
||||
timer:0 ,
|
||||
loading:false,
|
||||
showAdd:false
|
||||
});
|
||||
|
||||
const handleDel = (row) => {
|
||||
ElMessageBox.confirm(t('common.deleteText',[`[${row.PublicPort}:${props.protocolTypes[row.ProtocolType]}]`]), t('common.tips'), {
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
delUpnpMappingInfo(props.machineId,row.PublicPort,row.ProtocolType).then(res => {
|
||||
setTimeout(handleSearch,1000);
|
||||
ElMessage.success(t('common.oper'));
|
||||
});
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const getMapping = () => {
|
||||
if(state.loading) {
|
||||
state.timer = setTimeout(getMapping,5000);
|
||||
return;
|
||||
}
|
||||
clearTimeout(state.timer);
|
||||
state.loading = true;
|
||||
getUpnpMappingLocalInfo(props.machineId).then(res=>{
|
||||
state.loading = false;
|
||||
filter(res);
|
||||
state.timer = setTimeout(getMapping,5000);
|
||||
}).catch(()=>{
|
||||
state.loading = false;
|
||||
state.timer = setTimeout(getMapping,5000);
|
||||
});
|
||||
}
|
||||
const handleSearch = ()=>{
|
||||
if(state.loading) {
|
||||
return;
|
||||
}
|
||||
state.loading = true;
|
||||
getUpnpMappingLocalInfo(props.machineId).then(res=>{
|
||||
state.loading = false;
|
||||
filter(res);
|
||||
}).catch(()=>{
|
||||
state.loading = false;
|
||||
});
|
||||
}
|
||||
const filter = (res) => {
|
||||
state.list = res.filter(c=>{
|
||||
return c.Description.indexOf(state.name)>=0 ||
|
||||
c.ClientIp.indexOf(state.name)>=0 ||
|
||||
c.PublicPort.toString().indexOf(state.name)>=0 ||
|
||||
c.PrivatePort.toString().indexOf(state.name)>=0 ||
|
||||
props.protocolTypes[c.ProtocolType].indexOf(state.name)>=0 ||
|
||||
props.deviceTypes[c.DeviceType].indexOf(state.name)>=0;
|
||||
});
|
||||
}
|
||||
|
||||
const addState = ref({});
|
||||
provide('addState', addState);
|
||||
const handleAdd = () => {
|
||||
addState.value = {
|
||||
PublicPort: 0,
|
||||
PrivatePort: 0,
|
||||
ProtocolType: 6,
|
||||
Enabled: true,
|
||||
Description: 'linker tunnel',
|
||||
LeaseDuration: 7200,
|
||||
DeviceType: 255,
|
||||
Deletable: false,
|
||||
};
|
||||
state.showAdd = true;
|
||||
}
|
||||
const handleEnabledChange = (row) => {
|
||||
delUpnpMappingInfo(props.machineId,row.PublicPort,row.ProtocolType).then(res => {
|
||||
addUpnpMappingInfo(props.machineId,row).then(res=>{
|
||||
setTimeout(handleSearch,1000);
|
||||
ElMessage.success(t('common.oper'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
getMapping();
|
||||
});
|
||||
onUnmounted(()=>{
|
||||
clearTimeout(state.timer);
|
||||
});
|
||||
|
||||
return {
|
||||
state,handleDel,handleSearch,handleAdd,handleEnabledChange
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<el-dialog v-model="state.show" :close-on-click-modal="false" append-to=".app-wrap" title="UPNP" width="360" top="2vh">
|
||||
<div>
|
||||
<el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="auto">
|
||||
<el-form-item label="设备类型" prop="DeviceType">
|
||||
<el-select v-model="state.ruleForm.DeviceType" placeholder="请选择">
|
||||
<el-option v-for="(item,key) in deviceTypes" :key="Number(key)" :label="item" :value="Number(key)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议" prop="ProtocolType">
|
||||
<el-select v-model="state.ruleForm.ProtocolType" placeholder="请选择">
|
||||
<el-option v-for="(item,key) in protocolTypes" :key="Number(key)" :label="item" :value="Number(key)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="外网端口" prop="PublicPort">
|
||||
<el-input-number v-model="state.ruleForm.PublicPort" />
|
||||
</el-form-item>
|
||||
<el-form-item label="外网端口" prop="PrivatePort">
|
||||
<el-input-number v-model="state.ruleForm.PrivatePort" />
|
||||
</el-form-item>
|
||||
<el-form-item label="存活时间" prop="LeaseDuration">
|
||||
<el-input-number v-model="state.ruleForm.LeaseDuration" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="Description">
|
||||
<el-input v-model="state.ruleForm.Description" :maxlength="32" show-word-limit clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用" prop="Enabled">
|
||||
<el-switch v-model="state.ruleForm.Enabled" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="" prop="Btns">
|
||||
<div class="t-c w-100">
|
||||
<el-button @click="state.show = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">确认</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { addUpnpMappingInfo, delUpnpMappingInfo } from '@/apis/tunnel';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { inject, reactive, ref, watch } from 'vue';
|
||||
|
||||
export default {
|
||||
props: ['modelValue','deviceTypes','protocolTypes','machineId'],
|
||||
emits: ['change','update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const ruleFormRef = ref(null);
|
||||
const addState = inject('addState');
|
||||
const deletePort = addState.value.PublicPort || 0;
|
||||
const deleteProtocolType = addState.value.ProtocolType || 0;
|
||||
|
||||
const state = reactive({
|
||||
show: true,
|
||||
ruleForm: {
|
||||
PublicPort: addState.value.PublicPort || 0,
|
||||
PrivatePort: addState.value.PrivatePort || 0,
|
||||
ProtocolType: addState.value.ProtocolType || 6,
|
||||
Enabled: addState.value.Enabled,
|
||||
Description: addState.value.Description || '',
|
||||
LeaseDuration: addState.value.LeaseDuration || 7200,
|
||||
DeviceType: addState.value.DeviceType || 255,
|
||||
Deletable: true,
|
||||
},
|
||||
rules: {
|
||||
PublicPort: [
|
||||
{ required: true, message: '请输入外网端口', trigger: 'blur' },
|
||||
{ type: 'number',min: 1, max: 65535, message: '请输入数字1-65535', trigger: ['blur', 'change'] },
|
||||
],
|
||||
PrivatePort: [
|
||||
{ required: true, message: '请输入内网端口', trigger: 'blur' },
|
||||
{ type: 'number',min: 1, max: 65535, message: '请输入数字1-65535', trigger: ['blur', 'change'] }
|
||||
],
|
||||
LeaseDuration: [
|
||||
{ required: true, message: '请输入存活时间', trigger: 'blur' },
|
||||
{ type: 'number',min: 0, max: 2147483647, message: '请输入数字0-2147483647', trigger: ['blur', 'change'] }
|
||||
]
|
||||
},
|
||||
net:{}
|
||||
});
|
||||
watch(() => state.show, (val) => {
|
||||
if (!val) {
|
||||
setTimeout(() => {
|
||||
emit('update:modelValue', val);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
ruleFormRef.value.validate((valid) => {
|
||||
if(!valid) return;
|
||||
|
||||
const json = JSON.parse(JSON.stringify(state.ruleForm));
|
||||
json.PublicPort = +state.ruleForm.PublicPort;
|
||||
json.PrivatePort = +state.ruleForm.PrivatePort;
|
||||
json.ProtocolType = +state.ruleForm.ProtocolType;
|
||||
json.LeaseDuration = +state.ruleForm.LeaseDuration;
|
||||
json.DeviceType = +state.ruleForm.DeviceType;
|
||||
|
||||
delUpnpMappingInfo(props.machineId,deletePort,deleteProtocolType).then(() => {
|
||||
addUpnpMappingInfo(props.machineId,json).then(() => {
|
||||
state.show = false;
|
||||
ElMessage.success('已操作!');
|
||||
emit('change')
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
ElMessage.error('操作失败!');
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
ElMessage.error('操作失败!');
|
||||
});
|
||||
});
|
||||
}
|
||||
return {
|
||||
state, ruleFormRef, handleSave
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="head pdb-6 t-c">
|
||||
<el-input size="small" v-model="state.name" @change="handleSearch" clearable style="width:10rem;margin-right:1rem"></el-input>
|
||||
<el-button size="small" :loading="state.loading" @click="handleSearch"><el-icon><Search></Search></el-icon> </el-button>
|
||||
</div>
|
||||
<el-table stripe :data="state.list" border size="small" width="100%" height="60vh">
|
||||
<el-table-column prop="DeviceType" label="类型" width="66" sortable>
|
||||
<template #default="scope">{{ deviceTypes[scope.row.DeviceType] }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="PublicPort" label="外网端口" width="90" sortable></el-table-column>
|
||||
<el-table-column prop="ClientIp" label="内网ip" width="100" sortable></el-table-column>
|
||||
<el-table-column prop="PrivatePort" label="内网端口" width="90" sortable></el-table-column>
|
||||
<el-table-column prop="ProtocolType" label="协议" width="66" sortable>
|
||||
<template #default="scope">{{ protocolTypes[scope.row.ProtocolType] }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="LeaseDuration" label="存活" width="66" sortable>
|
||||
<template #default="scope">
|
||||
<span :class="{red:scope.row.LeaseDuration ==0,green:scope.row.LeaseDuration >0}">{{ scope.row.LeaseDuration }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="Description" label="描述" width="200"></el-table-column>
|
||||
<el-table-column property="Oper" label="" width="60" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="danger" v-if="scope.row.Deletable" @click="handleDel(scope.row)"><el-icon><DeleteFilled></DeleteFilled></el-icon> </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { onMounted, onUnmounted, reactive } from 'vue';
|
||||
import {delUpnpMappingInfo, getUpnpMappingInfo, getUpnpMappingLocalInfo } from '@/apis/tunnel';
|
||||
import {DeleteFilled,Search} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default {
|
||||
props: ['modelValue','deviceTypes','protocolTypes','machineId'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { DeleteFilled,Search },
|
||||
setup(props, { emit }) {
|
||||
|
||||
const { t } = useI18n();
|
||||
const state = reactive({
|
||||
name: '',
|
||||
list: [],
|
||||
timer:0 ,
|
||||
loading:false
|
||||
});
|
||||
|
||||
const handleDel = (row) => {
|
||||
ElMessageBox.confirm(t('common.deleteText',[`[${row.PublicPort}:${props.protocolTypes[row.ProtocolType]}]`]), t('common.tips'), {
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
delUpnpMappingInfo(props.machineId,row.PublicPort,row.ProtocolType).then(res => {
|
||||
setTimeout(handleSearch,1000);
|
||||
ElMessage.success(t('common.oper'));
|
||||
});
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
const getMapping = () => {
|
||||
if(state.loading) {
|
||||
state.timer = setTimeout(getMapping,5000);
|
||||
return;
|
||||
}
|
||||
clearTimeout(state.timer);
|
||||
state.loading = true;
|
||||
|
||||
Promise.all([
|
||||
getUpnpMappingInfo(props.machineId),
|
||||
getUpnpMappingLocalInfo(props.machineId)
|
||||
]).then(res=>{
|
||||
state.loading = false;
|
||||
filter(res);
|
||||
state.timer = setTimeout(getMapping,5000);
|
||||
}).catch(()=>{
|
||||
state.loading = false;
|
||||
state.timer = setTimeout(getMapping,5000);
|
||||
});
|
||||
}
|
||||
const handleSearch = ()=>{
|
||||
if(state.loading) {
|
||||
return;
|
||||
}
|
||||
state.loading = true;
|
||||
|
||||
Promise.all([
|
||||
getUpnpMappingInfo(props.machineId),
|
||||
getUpnpMappingLocalInfo(props.machineId)
|
||||
]).then(res=>{
|
||||
state.loading = false;
|
||||
filter(res);
|
||||
}).catch(()=>{
|
||||
state.loading = false;
|
||||
});
|
||||
}
|
||||
const filter = (res) => {
|
||||
const local = res[1].reduce((json,item,index)=>{
|
||||
json[`${item.PublicPort}->${item.ClientIp}->${item.ProtocolType}`] = item;
|
||||
return json;
|
||||
},{});
|
||||
|
||||
state.list = res[0].filter(c=>{
|
||||
const _local = local[`${c.PublicPort}->${c.ClientIp}->${c.ProtocolType}`];
|
||||
if(_local){
|
||||
c['_local'] = true;
|
||||
c['Deletable'] = _local['Deletable'];
|
||||
}
|
||||
return c.Description.indexOf(state.name)>=0 ||
|
||||
c.ClientIp.indexOf(state.name)>=0 ||
|
||||
c.PublicPort.toString().indexOf(state.name)>=0 ||
|
||||
c.PrivatePort.toString().indexOf(state.name)>=0 ||
|
||||
props.protocolTypes[c.ProtocolType].indexOf(state.name)>=0 ||
|
||||
props.deviceTypes[c.DeviceType].indexOf(state.name)>=0;
|
||||
}).sort((a,b)=>b['_local']-a['_local']);
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
getMapping();
|
||||
});
|
||||
onUnmounted(()=>{
|
||||
clearTimeout(state.timer);
|
||||
});
|
||||
|
||||
return {
|
||||
state, handleDel,handleSearch
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
</style>
|
||||
@@ -11,6 +11,7 @@ export const provideTunnel = () => {
|
||||
hashcode1: 0,
|
||||
|
||||
showEdit: false,
|
||||
showUpnp: false,
|
||||
current: null,
|
||||
});
|
||||
provide(tunnelSymbol, tunnel);
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
<DeviceEdit v-if="devices.showDeviceEdit" v-model="devices.showDeviceEdit" @change="handlePageChange" :data="devices.deviceInfo"></DeviceEdit>
|
||||
<AccessEdit v-if="devices.showAccessEdit" v-model="devices.showAccessEdit" @change="handlePageChange" :data="devices.deviceInfo"></AccessEdit>
|
||||
<TunnelEdit v-if="tunnel.showEdit" v-model="tunnel.showEdit" @change="deviceRefreshHook('tunnel')"></TunnelEdit>
|
||||
<TunnelUpnp v-if="tunnel.showUpnp" v-model="tunnel.showUpnp"></TunnelUpnp>
|
||||
|
||||
<ConnectionsEdit v-if="connections.showEdit" v-model="connections.showEdit" ></ConnectionsEdit>
|
||||
<TuntapEdit v-if="tuntap.showEdit" v-model="tuntap.showEdit" @change="deviceRefreshHook('tuntap')"></TuntapEdit>
|
||||
<TuntapLease v-if="tuntap.showLease" v-model="tuntap.showLease" @change="deviceRefreshHook('tuntap')"></TuntapLease>
|
||||
@@ -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,
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
3. #89 windows下利用任务计划进行进程守护,定时检查服务
|
||||
4. 优化客户端管理ws并发
|
||||
5. 减少服务器推送,减轻主服务器压力
|
||||
6. 优化中继,针对无法连接自己的外网IP的情况</Description>
|
||||
6. 优化中继,针对无法连接自己的外网IP的情况
|
||||
7. 优化upnp,自己实现了一遍</Description>
|
||||
<Copyright>snltty</Copyright>
|
||||
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
|
||||
|
||||
Reference in New Issue
Block a user