This commit is contained in:
snltty
2025-03-02 17:55:04 +08:00
parent b49bd135bf
commit d12d4a1b99
49 changed files with 1082 additions and 247 deletions

View File

@@ -35,7 +35,7 @@ jobs:
release_name: v1.6.9.${{ steps.date.outputs.today }} release_name: v1.6.9.${{ steps.date.outputs.today }}
draft: false draft: false
prerelease: false prerelease: false
body: "1. 修复litedb抢锁超时导致客户端登录失败问题\r\n2. 同步信标服务器\r\n3. 其它一些修复优化" body: "1. 优化linux下路由跟踪问题\r\n2. 优化linux下获取本机IP问题\r\n3. 增加ICS让win7+、win server2008+支持NAT\r\n4. 增加中继卡密\r\n5. 增加内外穿透定时开关功能\r\n6. 优化管理页面连接接口的体验\r\n7. 优化一些UI体验去除同步页面将同步功能放置各个实际的位置\r\n8. 其它一些修复优化"
- name: publish projects - name: publish projects
run: ./publish.bat run: ./publish.bat
- name: upload-win-x86-oss - name: upload-win-x86-oss

View File

@@ -57,10 +57,11 @@ sidebar_position: 1
::: :::
:::danger[win10以下] :::danger[win10以下]
1. win7 或 win8 可能需要安装一些环境,才能运行 1. win7、win8、win server 2008、win server 2008 R2 可能需要安装一些环境,才能运行
2. <a href="https://aka.ms/vs/16/release/vc_redist.x64.exe" target="_blank">Microsoft Visual C++ 2015-2019 Redistributable </a> 2. <a href="https://aka.ms/vs/16/release/vc_redist.x64.exe" target="_blank">Microsoft Visual C++ 2015-2019 Redistributable 运行库</a>
3. <a href="https://www.microsoft.com/download/details.aspx?id=47442" target="_blank">KB3063858 </a> 3. <a href="https://www.microsoft.com/download/details.aspx?id=47442" target="_blank">KB3063858 运行库</a>
4. <a href="https://www.microsoft.com/zh-cn/download/details.aspx?id=46148" target="_blank">KB3033929 </a> 4. <a href="https://www.microsoft.com/zh-cn/download/details.aspx?id=46148" target="_blank">KB3033929 全球化补丁</a>
5. <a href="https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-web-installer" target="_blank">netframework4.6.2 ICS NAT运行库</a>
::: :::

View File

@@ -1,24 +0,0 @@
---
sidebar_position: 4
---
# 1.1.2、RRAS
:::tip[说明]
1. 操作不当可能会导致网络无法访问,请谨慎操作
2. 有局限性,创建虚拟网卡后,需要重新`“配置并启用路由和远程访问”`
![](./img/rras1.png)
![](./img/rras2.png)
![](./img/rras3.png)
![](./img/rras4.png)
![](./img/rras5.png)
![](./img/rras6.png)
![](./img/rras7.png)
![](./img/rras8.png)
![](./img/rras9.png)
![](./img/rras10.png)
![](./img/rras11.png)
![](./img/rras12.png)
![](./img/rras13.png)
:::

View File

@@ -1,12 +1,11 @@
--- ---
sidebar_position: 5 sidebar_position: 4
--- ---
# 1.1.3、ICS # 1.1.3、ICS
:::tip[说明] :::tip[说明]
1. 操作不当可能会导致网络无法访问,请谨慎操作 1. 如果系统没有<a href="https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-web-installer" target="_blank">netframework4.6.2</a>,就下载安装一下
2. 需要linker v1.7.0+版本
![](./img/ics1.png) 3. 剩下的交给linker
::: :::

View File

@@ -1,5 +1,5 @@
--- ---
sidebar_position: 6 sidebar_position: 5
--- ---
# 1.2、网对网 # 1.2、网对网

View File

@@ -9,9 +9,9 @@ namespace linker.libs
{ {
public static string Windows(string arg, string[] commands) public static string Windows(string arg, string[] commands)
{ {
return Execute("cmd.exe", arg, commands,out string error); return Execute("cmd.exe", arg, commands, out string error);
} }
public static string Windows(string arg, string[] commands,out string error) public static string Windows(string arg, string[] commands, out string error)
{ {
return Execute("cmd.exe", arg, commands, out error); return Execute("cmd.exe", arg, commands, out error);
} }
@@ -23,14 +23,14 @@ namespace linker.libs
error = "PowerShell is not installed"; error = "PowerShell is not installed";
return string.Empty; return string.Empty;
} }
return Execute("powershell.exe", arg, commands,out error); return Execute("powershell.exe", arg, commands, out error);
} }
public static string Linux(string arg, string[] commands) public static string Linux(string arg, string[] commands)
{ {
return Execute("/bin/bash", arg, commands, out string error); return Execute("/bin/bash", arg, commands, out string error);
} }
public static string Linux(string arg, string[] commands,out string error) public static string Linux(string arg, string[] commands, out string error)
{ {
return Execute("/bin/bash", arg, commands, out error); return Execute("/bin/bash", arg, commands, out error);
} }
@@ -56,7 +56,7 @@ namespace linker.libs
return proc; return proc;
} }
public static string Execute(string fileName, string arg, string[] commands,out string error) public static string Execute(string fileName, string arg, string[] commands, out string error)
{ {
using Process proc = new Process(); using Process proc = new Process();
proc.StartInfo.WorkingDirectory = Path.GetFullPath(Path.Join("./")); proc.StartInfo.WorkingDirectory = Path.GetFullPath(Path.Join("./"));
@@ -80,12 +80,9 @@ namespace linker.libs
proc.StandardInput.WriteLine("exit"); proc.StandardInput.WriteLine("exit");
proc.StandardInput.Close(); proc.StandardInput.Close();
string output = proc.StandardOutput.ReadToEnd();
error = proc.StandardError.ReadToEnd(); error = proc.StandardError.ReadToEnd();
string output = string.Empty;
if (string.IsNullOrWhiteSpace(error))
{
output = proc.StandardOutput.ReadToEnd();
}
proc.WaitForExit(); proc.WaitForExit();
proc.Close(); proc.Close();
proc.Dispose(); proc.Dispose();

View File

@@ -142,7 +142,7 @@ namespace linker.libs
{ {
result = new List<IPAddress>(); result = new List<IPAddress>();
string str = CommandHelper.Linux(string.Empty, new string[] { $"traceroute {server} -4 -m 5" }); string str = CommandHelper.Linux(string.Empty, new string[] { $"traceroute {server} -4 -m 5 -w 1" });
string[] lines = str.Split(Environment.NewLine); string[] lines = str.Split(Environment.NewLine);
Regex regex = new Regex(@"(\d+\.\d+\.\d+\.\d+)"); Regex regex = new Regex(@"(\d+\.\d+\.\d+\.\d+)");
@@ -191,34 +191,42 @@ namespace linker.libs
private static byte[] ipv6LocalBytes = new byte[] { 254, 128, 0, 0, 0, 0, 0, 0 }; private static byte[] ipv6LocalBytes = new byte[] { 254, 128, 0, 0, 0, 0, 0, 0 };
public static IPAddress[] GetIPV6()
private static IPAddress[] GetIP()
{ {
try try
{ {
return Dns.GetHostAddresses(Dns.GetHostName()) return Dns.GetHostEntry(Dns.GetHostName()).AddressList;
.Where(c => c.AddressFamily == AddressFamily.InterNetworkV6) }
.Where(c => c.GetAddressBytes().AsSpan(0, 8).SequenceEqual(ipv6LocalBytes) == false).Distinct().ToArray(); catch (Exception)
{
try
{
return NetworkInterface.GetAllNetworkInterfaces()
.SelectMany(c => c.GetIPProperties().UnicastAddresses)
.Select(c => c.Address)
.ToArray();
} }
catch (Exception) catch (Exception)
{ {
} }
}
return Array.Empty<IPAddress>(); return Array.Empty<IPAddress>();
} }
public static IPAddress[] GetIPV6()
{
return GetIP()
.Where(c => c.AddressFamily == AddressFamily.InterNetworkV6)
.Where(c => c.GetAddressBytes().AsSpan(0, 8).SequenceEqual(ipv6LocalBytes) == false).Distinct().ToArray(); ;
}
public static IPAddress[] GetIPV4() public static IPAddress[] GetIPV4()
{ {
try return GetIP()
{ .Where(c => c.AddressFamily == AddressFamily.InterNetwork)
return Dns.GetHostEntry(Dns.GetHostName()).AddressList
.Where(c => c.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
.Where(c => c.IsIPv4MappedToIPv6 == false) .Where(c => c.IsIPv4MappedToIPv6 == false)
.Where(c => c.Equals(IPAddress.Loopback) == false) .Where(c => c.Equals(IPAddress.Loopback) == false)
.Distinct().ToArray(); .Distinct().ToArray();
} }
catch (Exception)
{
}
return Array.Empty<IPAddress>();
}
public static byte ToPrefixLength(uint ip) public static byte ToPrefixLength(uint ip)
{ {

View File

@@ -38,7 +38,7 @@ namespace linker.libs.api
server = new WebSocketServer(); server = new WebSocketServer();
try try
{ {
server.Start( port); server.Start(port);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -51,6 +51,13 @@ namespace linker.libs.api
{ {
header.SetHeaderValue(WebsocketHeaderKey.SecWebSocketExtensions, string.Empty); header.SetHeaderValue(WebsocketHeaderKey.SecWebSocketExtensions, string.Empty);
} }
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
header.TryGetHeaderValue(WebsocketHeaderKey.SecWebSocketProtocol, out string _password1);
LoggerHelper.Instance.Info($"websocket client password {_password1} eq {password}");
}
return res; return res;
}; };
server.OnOpen = (connection) => server.OnOpen = (connection) =>

View File

@@ -313,6 +313,8 @@ namespace linker.libs.websocket
{ {
token.Connectrion.SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.ExtendsError); token.Connectrion.SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.ExtendsError);
token.Connectrion.Close(); token.Connectrion.Close();
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"websocket opcode error:{token.FrameInfo.Opcode}");
return; return;
} }
} }
@@ -356,6 +358,8 @@ namespace linker.libs.websocket
header.StatusCode = HttpStatusCode.MethodNotAllowed; header.StatusCode = HttpStatusCode.MethodNotAllowed;
token.Connectrion.ConnectResponse(header); token.Connectrion.ConnectResponse(header);
token.Connectrion.Close(); token.Connectrion.Close();
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error("websocket SecWebSocketKey error");
return; return;
} }
@@ -370,6 +374,8 @@ namespace linker.libs.websocket
header.StatusCode = HttpStatusCode.Unauthorized; header.StatusCode = HttpStatusCode.Unauthorized;
token.Connectrion.ConnectResponse(header); token.Connectrion.ConnectResponse(header);
token.Connectrion.Close(); token.Connectrion.Close();
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error("websocket OnConnecting false");
} }
} }
@@ -479,6 +485,9 @@ namespace linker.libs.websocket
{ {
Disposabled = true; Disposabled = true;
Connectrion.Close(); Connectrion.Close();
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"websocket connection clear");
} }
} }

View File

@@ -136,6 +136,9 @@
[AccessDisplay("重置所有接口密码")] [AccessDisplay("重置所有接口密码")]
SetApiPasswordOther = (ulong)1 << 42, SetApiPasswordOther = (ulong)1 << 42,
[AccessDisplay("管理中继CDKEY")]
RelayCdkey = (ulong)1 << 43,
Full = ulong.MaxValue >> 64 - 52, Full = ulong.MaxValue >> 64 - 52,
} }

View File

@@ -1,9 +1,12 @@
using linker.libs.api; using linker.libs;
using linker.libs.api;
using linker.libs.extends; using linker.libs.extends;
using linker.messenger.api; using linker.messenger.api;
using linker.messenger.relay.client; using linker.messenger.relay.client;
using linker.messenger.relay.client.transport; using linker.messenger.relay.client.transport;
using linker.messenger.relay.messenger;
using linker.messenger.relay.server; using linker.messenger.relay.server;
using linker.messenger.signin;
namespace linker.messenger.relay namespace linker.messenger.relay
{ {
@@ -15,19 +18,23 @@ namespace linker.messenger.relay
private readonly RelayClientTestTransfer relayTestTransfer; private readonly RelayClientTestTransfer relayTestTransfer;
private readonly RelayClientTransfer relayTransfer; private readonly RelayClientTransfer relayTransfer;
private readonly IRelayClientStore relayClientStore; private readonly IRelayClientStore relayClientStore;
private readonly SignInClientState signInClientState;
private readonly IMessengerSender messengerSender;
private readonly ISerializer serializer;
private readonly ISignInClientStore signInClientStore;
public RelayApiController(RelayClientTestTransfer relayTestTransfer, RelayClientTransfer relayTransfer, IRelayClientStore relayClientStore) public RelayApiController(RelayClientTestTransfer relayTestTransfer, RelayClientTransfer relayTransfer, IRelayClientStore relayClientStore,
SignInClientState signInClientState, IMessengerSender messengerSender, ISerializer serializer, ISignInClientStore signInClientStore)
{ {
this.relayTestTransfer = relayTestTransfer; this.relayTestTransfer = relayTestTransfer;
this.relayTransfer = relayTransfer; this.relayTransfer = relayTransfer;
this.relayClientStore = relayClientStore; this.relayClientStore = relayClientStore;
this.signInClientState = signInClientState;
this.messengerSender = messengerSender;
this.serializer = serializer;
this.signInClientStore = signInClientStore;
} }
/// <summary>
/// 设置中继协议
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
[Access(AccessValue.Config)] [Access(AccessValue.Config)]
public bool SetServers(ApiControllerParamsInfo param) public bool SetServers(ApiControllerParamsInfo param)
{ {
@@ -50,6 +57,90 @@ namespace linker.messenger.relay
return true; return true;
} }
public async Task<bool> AccessCdkey(ApiControllerParamsInfo param)
{
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.AccessCdkey,
Payload = serializer.Serialize(relayClientStore.Server.SecretKey)
});
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
[Access(AccessValue.RelayCdkey)]
public async Task<bool> AddCdkey(ApiControllerParamsInfo param)
{
RelayServerCdkeyInfo info = param.Content.DeJson<RelayServerCdkeyInfo>();
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.AddCdkey,
Payload = serializer.Serialize(new RelayServerCdkeyAddInfo
{
Data = info,
SecretKey = relayClientStore.Server.SecretKey
})
});
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
[Access(AccessValue.RelayCdkey)]
public async Task<bool> DelCdkey(ApiControllerParamsInfo param)
{
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.DelCdkey,
Payload = serializer.Serialize(new RelayServerCdkeyDelInfo
{
Id = param.Content,
SecretKey = relayClientStore.Server.SecretKey
})
});
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
[Access(AccessValue.RelayCdkey)]
public async Task<RelayServerCdkeyPageResultInfo> PageCdkey(ApiControllerParamsInfo param)
{
RelayServerCdkeyPageRequestInfo info = param.Content.DeJson<RelayServerCdkeyPageRequestInfo>();
info.SecretKey = relayClientStore.Server.SecretKey;
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.PageCdkey,
Payload = serializer.Serialize(info)
});
if (resp.Code == MessageResponeCodes.OK)
{
return serializer.Deserialize<RelayServerCdkeyPageResultInfo>(resp.Data.Span);
}
return new RelayServerCdkeyPageResultInfo();
}
public async Task<RelayServerCdkeyPageResultInfo> MyCdkey(ApiControllerParamsInfo param)
{
RelayServerCdkeyPageRequestInfo info = param.Content.DeJson<RelayServerCdkeyPageRequestInfo>();
info.SecretKey = relayClientStore.Server.SecretKey;
info.UserId = signInClientStore.Server.UserId;
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.PageCdkey,
Payload = serializer.Serialize(info)
});
if (resp.Code == MessageResponeCodes.OK)
{
return serializer.Deserialize<RelayServerCdkeyPageResultInfo>(resp.Data.Span);
}
return new RelayServerCdkeyPageResultInfo();
}
} }
public sealed class RelayConnectInfo public sealed class RelayConnectInfo

View File

@@ -18,9 +18,11 @@ namespace linker.messenger.relay.client
private Dictionary<string, List<Action<ITunnelConnection>>> OnConnected { get; } = new Dictionary<string, List<Action<ITunnelConnection>>>(); private Dictionary<string, List<Action<ITunnelConnection>>> OnConnected { get; } = new Dictionary<string, List<Action<ITunnelConnection>>>();
private readonly IRelayClientStore relayClientStore; private readonly IRelayClientStore relayClientStore;
public RelayClientTransfer(IMessengerSender messengerSender,ISerializer serializer,IRelayClientStore relayClientStore,SignInClientState signInClientState,IMessengerStore messengerStore) private readonly ISignInClientStore signInClientStore;
public RelayClientTransfer(IMessengerSender messengerSender, ISerializer serializer, IRelayClientStore relayClientStore, SignInClientState signInClientState, IMessengerStore messengerStore, ISignInClientStore signInClientStore)
{ {
this.relayClientStore = relayClientStore; this.relayClientStore = relayClientStore;
this.signInClientStore = signInClientStore;
Transports = new List<IRelayClientTransport> { Transports = new List<IRelayClientTransport> {
new RelayClientTransportSelfHost(messengerSender,serializer,relayClientStore,signInClientState,messengerStore), new RelayClientTransportSelfHost(messengerSender,serializer,relayClientStore,signInClientState,messengerStore),
}; };
@@ -87,7 +89,8 @@ namespace linker.messenger.relay.client
TransactionId = transactionId, TransactionId = transactionId,
TransportName = transport.Name, TransportName = transport.Name,
SSL = relayClientStore.Server.SSL, SSL = relayClientStore.Server.SSL,
NodeId = nodeId NodeId = nodeId,
UserId = signInClientStore.Server.UserId,
}; };
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)

View File

@@ -112,6 +112,11 @@ namespace linker.messenger.relay.client.transport
/// 是否ssl /// 是否ssl
/// </summary> /// </summary>
public bool SSL { get; set; } = true; public bool SSL { get; set; } = true;
/// <summary>
/// UserId
/// </summary>
public string UserId { get; set; }
} }

View File

@@ -45,14 +45,18 @@ namespace linker.messenger.relay.messenger
private readonly RelayServerMasterTransfer relayServerTransfer; private readonly RelayServerMasterTransfer relayServerTransfer;
private readonly RelayServerValidatorTransfer relayValidatorTransfer; private readonly RelayServerValidatorTransfer relayValidatorTransfer;
private readonly ISerializer serializer; private readonly ISerializer serializer;
private readonly IRelayServerCdkeyStore relayServerCdkeyStore;
private readonly IRelayServerStore relayServerStore;
public RelayServerMessenger(IMessengerSender messengerSender, SignInServerCaching signCaching, ISerializer serializer, RelayServerMasterTransfer relayServerTransfer, RelayServerValidatorTransfer relayValidatorTransfer) public RelayServerMessenger(IMessengerSender messengerSender, SignInServerCaching signCaching, ISerializer serializer, RelayServerMasterTransfer relayServerTransfer, RelayServerValidatorTransfer relayValidatorTransfer, IRelayServerCdkeyStore relayServerCdkeyStore, IRelayServerStore relayServerStore)
{ {
this.messengerSender = messengerSender; this.messengerSender = messengerSender;
this.signCaching = signCaching; this.signCaching = signCaching;
this.relayServerTransfer = relayServerTransfer; this.relayServerTransfer = relayServerTransfer;
this.relayValidatorTransfer = relayValidatorTransfer; this.relayValidatorTransfer = relayValidatorTransfer;
this.serializer = serializer; this.serializer = serializer;
this.relayServerCdkeyStore = relayServerCdkeyStore;
this.relayServerStore = relayServerStore;
} }
/// <summary> /// <summary>
@@ -89,7 +93,7 @@ namespace linker.messenger.relay.messenger
[MessengerId((ushort)RelayMessengerIds.RelayAsk)] [MessengerId((ushort)RelayMessengerIds.RelayAsk)]
public async Task RelayAsk(IConnection connection) public async Task RelayAsk(IConnection connection)
{ {
client.transport.RelayInfo info = serializer.Deserialize<client.transport.RelayInfo>(connection.ReceiveRequestWrap.Payload.Span); RelayInfo info = serializer.Deserialize<RelayInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cacheFrom) == false || signCaching.TryGet(info.RemoteMachineId, out SignCacheInfo cacheTo) == false || cacheFrom.GroupId != cacheTo.GroupId) if (signCaching.TryGet(connection.Id, out SignCacheInfo cacheFrom) == false || signCaching.TryGet(info.RemoteMachineId, out SignCacheInfo cacheTo) == false || cacheFrom.GroupId != cacheTo.GroupId)
{ {
connection.Write(serializer.Serialize(new RelayAskResultInfo { })); connection.Write(serializer.Serialize(new RelayAskResultInfo { }));
@@ -105,9 +109,11 @@ namespace linker.messenger.relay.messenger
string error = await relayValidatorTransfer.Validate(info, cacheFrom, cacheTo); string error = await relayValidatorTransfer.Validate(info, cacheFrom, cacheTo);
result.Nodes = relayServerTransfer.GetNodes(string.IsNullOrWhiteSpace(error)); result.Nodes = relayServerTransfer.GetNodes(string.IsNullOrWhiteSpace(error));
List<RelayServerCdkeyInfo> cdkeys = await relayServerCdkeyStore.Get(info.UserId);
if (result.Nodes.Count > 0) if (result.Nodes.Count > 0)
{ {
result.FlowingId = relayServerTransfer.AddRelay(cacheFrom.MachineId, cacheFrom.MachineName, cacheTo.MachineId, cacheTo.MachineName, cacheFrom.GroupId); result.FlowingId = relayServerTransfer.AddRelay(cacheFrom.MachineId, cacheFrom.MachineName, cacheTo.MachineId, cacheTo.MachineName, cacheFrom.GroupId, cdkeys);
} }
connection.Write(serializer.Serialize(result)); connection.Write(serializer.Serialize(result));
@@ -167,5 +173,89 @@ namespace linker.messenger.relay.messenger
connection.Write(Helper.FalseArray); connection.Write(Helper.FalseArray);
} }
} }
/// <summary>
/// 检查权限
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)RelayMessengerIds.AccessCdkey)]
public void AccessCdkey(IConnection connection)
{
string secretKey = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
connection.Write(relayServerStore.SecretKey == secretKey ? Helper.TrueArray : Helper.FalseArray);
}
/// <summary>
/// 添加CDKEY
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)RelayMessengerIds.AddCdkey)]
public async Task AddCdkey(IConnection connection)
{
RelayServerCdkeyAddInfo info = serializer.Deserialize<RelayServerCdkeyAddInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(Helper.FalseArray);
return;
}
if (relayServerStore.SecretKey != info.SecretKey)
{
connection.Write(Helper.FalseArray);
return;
}
await relayServerCdkeyStore.Add(info.Data);
connection.Write(Helper.TrueArray);
}
/// <summary>
/// 删除Cdkey
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)RelayMessengerIds.DelCdkey)]
public async Task DelCdkey(IConnection connection)
{
RelayServerCdkeyDelInfo info = serializer.Deserialize<RelayServerCdkeyDelInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(Helper.FalseArray);
return;
}
if (relayServerStore.SecretKey != info.SecretKey)
{
connection.Write(Helper.FalseArray);
return;
}
await relayServerCdkeyStore.Del(info.Id);
connection.Write(Helper.TrueArray);
}
/// <summary>
/// 查询CDKEY
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)RelayMessengerIds.PageCdkey)]
public async Task PageCdkey(IConnection connection)
{
RelayServerCdkeyPageRequestInfo info = serializer.Deserialize<RelayServerCdkeyPageRequestInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { }));
return;
}
if (relayServerStore.SecretKey != info.SecretKey && string.IsNullOrWhiteSpace(info.UserId))
{
connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { }));
return;
}
var page = await relayServerCdkeyStore.Get(info);
connection.Write(serializer.Serialize(page));
}
} }
} }

View File

@@ -15,6 +15,11 @@
NodeDelay = 2106, NodeDelay = 2106,
NodeDelayForward = 2107, NodeDelayForward = 2107,
AddCdkey = 2108,
PageCdkey = 2109,
DelCdkey = 2110,
AccessCdkey = 2111,
Max = 2199 Max = 2199
} }
} }

View File

@@ -0,0 +1,104 @@
namespace linker.messenger.relay.server
{
public interface IRelayServerCdkeyStore
{
public Task<bool> Add(RelayServerCdkeyInfo info);
public Task<bool> Del(string id);
/// <summary>
/// 获取有效的CDKEY
/// </summary>
/// <param name="userid"></param>
/// <returns></returns>
public Task<List<RelayServerCdkeyInfo>> Get(string userid);
public Task<RelayServerCdkeyPageResultInfo> Get(RelayServerCdkeyPageRequestInfo relayServerCdkeyPageRequestInfo);
}
public sealed partial class RelayServerCdkeyPageRequestInfo
{
public int Page { get; set; }
public int Size { get; set; }
public string Order { get; set; }
public string Sort { get; set; }
public string UserId { get; set; }
public string Remark { get; set; }
public string SecretKey { get; set; }
}
public sealed partial class RelayServerCdkeyPageResultInfo
{
public int Page { get; set; }
public int Size { get; set; }
public int Count { get; set; }
public List<RelayServerCdkeyInfo> List { get; set; }
}
public sealed partial class RelayServerCdkeyAddInfo
{
public string SecretKey { get; set; }
public RelayServerCdkeyInfo Data { get; set; }
}
public sealed partial class RelayServerCdkeyDelInfo
{
public string SecretKey { get; set; }
public string Id { get; set; }
}
/// <summary>
/// 中继CDKEY
/// </summary>
public sealed partial class RelayServerCdkeyInfo
{
public string Id { get; set; }
/// <summary>
/// 用户标识
/// </summary>
public string UserId { get; set; }
/// <summary>
/// KEY
/// </summary>
public string CdKey { get; set; }
/// <summary>
/// 添加时间
/// </summary>
public DateTime AddTime { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public DateTime StartTime { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTime EndTime { get; set; }
/// <summary>
/// 允许节点
/// </summary>
public List<string> Nodes { get; set; }
/// <summary>
/// 带宽Mbps
/// </summary>
public double Bandwidth { get; set; }
/// <summary>
/// 流量
/// </summary>
public ulong MaxBytes { get; set; }
/// <summary>
/// 剩余流量
/// </summary>
public ulong LastBytes { get; set; }
/// <summary>
/// 原价
/// </summary>
public double Memory { get; set; }
/// <summary>
/// 支付金额
/// </summary>
public double PayMemory { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; }
}
}

View File

@@ -26,7 +26,7 @@ namespace linker.messenger.relay.server
} }
public ulong AddRelay(string fromid, string fromName, string toid, string toName, string groupid) public ulong AddRelay(string fromid, string fromName, string toid, string toName, string groupid, List<RelayServerCdkeyInfo> cdkeys)
{ {
ulong flowingId = Interlocked.Increment(ref relayFlowingId); ulong flowingId = Interlocked.Increment(ref relayFlowingId);
@@ -37,7 +37,8 @@ namespace linker.messenger.relay.server
FromName = fromName, FromName = fromName,
ToId = toid, ToId = toid,
ToName = toName, ToName = toName,
GroupId = groupid GroupId = groupid,
Cdkey = cdkeys
}; };
bool added = relayCaching.TryAdd($"{fromid}->{toid}->{flowingId}", cache, 15000); bool added = relayCaching.TryAdd($"{fromid}->{toid}->{flowingId}", cache, 15000);
if (added == false) return 0; if (added == false) return 0;
@@ -81,7 +82,7 @@ namespace linker.messenger.relay.server
} }
catch (Exception ex) catch (Exception ex)
{ {
if(LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{ {
LoggerHelper.Instance.Error(ex); LoggerHelper.Instance.Error(ex);
} }

View File

@@ -256,6 +256,8 @@ namespace linker.messenger.relay.server
public string ToId { get; set; } public string ToId { get; set; }
public string ToName { get; set; } public string ToName { get; set; }
public string GroupId { get; set; } public string GroupId { get; set; }
public List<RelayServerCdkeyInfo> Cdkey { get; set; }
} }
public sealed class RelayWrapInfo public sealed class RelayWrapInfo

View File

@@ -55,6 +55,12 @@ namespace linker.messenger.serializer.memorypack
MemoryPackFormatterProvider.Register(new RelayAskResultInfoFormatter()); MemoryPackFormatterProvider.Register(new RelayAskResultInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayCacheInfoFormatter()); MemoryPackFormatterProvider.Register(new RelayCacheInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayMessageInfoFormatter()); MemoryPackFormatterProvider.Register(new RelayMessageInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerCdkeyInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerCdkeyPageRequestInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerCdkeyPageResultInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerCdkeyAddInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerCdkeyDelInfoFormatter());
MemoryPackFormatterProvider.Register(new AccessUpdateInfoFormatter()); MemoryPackFormatterProvider.Register(new AccessUpdateInfoFormatter());
MemoryPackFormatterProvider.Register(new AccessInfoFormatter()); MemoryPackFormatterProvider.Register(new AccessInfoFormatter());

View File

@@ -89,11 +89,14 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackInclude] [MemoryPackInclude]
bool SSL => info.SSL; bool SSL => info.SSL;
[MemoryPackInclude]
string UserId => info.UserId;
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableRelayInfo(string fromMachineId, string fromMachineName, SerializableRelayInfo(string fromMachineId, string fromMachineName,
string remoteMachineId, string remoteMachineName, string remoteMachineId, string remoteMachineName,
string transactionId, string secretKey, string transportName, ulong flowingId, string transactionId, string secretKey, string transportName, ulong flowingId,
string nodeId, IPEndPoint server, bool ssl) string nodeId, IPEndPoint server, bool ssl, string userid)
{ {
var info = new RelayInfo var info = new RelayInfo
{ {
@@ -107,7 +110,8 @@ namespace linker.messenger.serializer.memorypack
TransactionId = transactionId, TransactionId = transactionId,
TransportName = transportName, TransportName = transportName,
SecretKey = secretKey, SecretKey = secretKey,
Server = server Server = server,
UserId = userid
}; };
this.info = info; this.info = info;
} }
@@ -295,7 +299,6 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackable] [MemoryPackable]
public readonly partial struct SerializableRelayCacheInfo public readonly partial struct SerializableRelayCacheInfo
{ {
@@ -315,8 +318,11 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackInclude] [MemoryPackInclude]
string GroupId => info.GroupId; string GroupId => info.GroupId;
[MemoryPackInclude, MemoryPackAllowSerialize]
List<RelayServerCdkeyInfo> Cdkey => info.Cdkey;
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableRelayCacheInfo(ulong flowId, string fromId, string fromName, string toId, string toName, string groupId) SerializableRelayCacheInfo(ulong flowId, string fromId, string fromName, string toId, string toName, string groupId, List<RelayServerCdkeyInfo> cdkey)
{ {
var info = new RelayCacheInfo var info = new RelayCacheInfo
{ {
@@ -326,6 +332,7 @@ namespace linker.messenger.serializer.memorypack
GroupId = groupId, GroupId = groupId,
ToId = toId, ToId = toId,
ToName = toName, ToName = toName,
Cdkey = cdkey
}; };
this.info = info; this.info = info;
} }
@@ -427,4 +434,340 @@ namespace linker.messenger.serializer.memorypack
value = wrapped.info; value = wrapped.info;
} }
} }
[MemoryPackable]
public readonly partial struct SerializableRelayServerCdkeyInfo
{
[MemoryPackIgnore]
public readonly RelayServerCdkeyInfo info;
[MemoryPackInclude]
string Id => info.Id;
[MemoryPackInclude]
string UserId => info.UserId;
[MemoryPackInclude]
string CdKey => info.CdKey;
[MemoryPackInclude]
DateTime AddTime => info.AddTime;
[MemoryPackInclude]
DateTime StartTime => info.StartTime;
[MemoryPackInclude]
DateTime EndTime => info.EndTime;
[MemoryPackInclude]
List<string> Nodes => info.Nodes;
[MemoryPackInclude]
double Bandwidth => info.Bandwidth;
[MemoryPackInclude]
ulong MaxBytes => info.MaxBytes;
[MemoryPackInclude]
ulong LastBytes => info.LastBytes;
[MemoryPackInclude]
double Memory => info.Memory;
[MemoryPackInclude]
double PayMemory => info.PayMemory;
[MemoryPackInclude]
string Remark => info.Remark;
[MemoryPackConstructor]
SerializableRelayServerCdkeyInfo(string id, string userid, string cdKey, DateTime addTime, DateTime startTime, DateTime endTime,
List<string> nodes, double bandwidth, ulong maxBytes, ulong lastBytes, double memory, double payMemory, string remark)
{
var info = new RelayServerCdkeyInfo
{
Id = id,
UserId = userid,
CdKey = cdKey,
AddTime = addTime,
StartTime = startTime,
EndTime = endTime,
Nodes = nodes,
Bandwidth = bandwidth,
MaxBytes = maxBytes,
LastBytes = lastBytes,
Memory = memory,
PayMemory = payMemory,
Remark = remark
};
this.info = info;
}
public SerializableRelayServerCdkeyInfo(RelayServerCdkeyInfo info)
{
this.info = info;
}
}
public class RelayServerCdkeyInfoFormatter : MemoryPackFormatter<RelayServerCdkeyInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerCdkeyInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerCdkeyInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerCdkeyInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerCdkeyAddInfo
{
[MemoryPackIgnore]
public readonly RelayServerCdkeyAddInfo info;
[MemoryPackInclude]
string SecretKey => info.SecretKey;
[MemoryPackInclude, MemoryPackAllowSerialize]
RelayServerCdkeyInfo Data => info.Data;
[MemoryPackConstructor]
SerializableRelayServerCdkeyAddInfo(string secretKey, RelayServerCdkeyInfo data)
{
var info = new RelayServerCdkeyAddInfo
{
SecretKey = secretKey,
Data = data
};
this.info = info;
}
public SerializableRelayServerCdkeyAddInfo(RelayServerCdkeyAddInfo info)
{
this.info = info;
}
}
public class RelayServerCdkeyAddInfoFormatter : MemoryPackFormatter<RelayServerCdkeyAddInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerCdkeyAddInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerCdkeyAddInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyAddInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerCdkeyAddInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerCdkeyDelInfo
{
[MemoryPackIgnore]
public readonly RelayServerCdkeyDelInfo info;
[MemoryPackInclude]
string SecretKey => info.SecretKey;
[MemoryPackInclude]
string Id => info.Id;
[MemoryPackConstructor]
SerializableRelayServerCdkeyDelInfo(string secretKey, string id)
{
var info = new RelayServerCdkeyDelInfo
{
SecretKey = secretKey,
Id = id
};
this.info = info;
}
public SerializableRelayServerCdkeyDelInfo(RelayServerCdkeyDelInfo info)
{
this.info = info;
}
}
public class RelayServerCdkeyDelInfoFormatter : MemoryPackFormatter<RelayServerCdkeyDelInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerCdkeyDelInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerCdkeyDelInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyDelInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerCdkeyDelInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerCdkeyPageRequestInfo
{
[MemoryPackIgnore]
public readonly RelayServerCdkeyPageRequestInfo info;
[MemoryPackInclude]
int Page => info.Page;
[MemoryPackInclude]
int Size => info.Size;
[MemoryPackInclude]
string Order => info.Order;
[MemoryPackInclude]
string Sort => info.Sort;
[MemoryPackInclude]
string UserId => info.UserId;
[MemoryPackInclude]
string Remark => info.Remark;
[MemoryPackInclude]
string SecretKey => info.SecretKey;
[MemoryPackConstructor]
SerializableRelayServerCdkeyPageRequestInfo(int page, int size, string order, string sort, string userid, string remark, string secretKey)
{
var info = new RelayServerCdkeyPageRequestInfo
{
Sort = sort,
Order = order,
Size = size,
Page = page,
UserId = userid,
Remark = remark,
SecretKey = secretKey
};
this.info = info;
}
public SerializableRelayServerCdkeyPageRequestInfo(RelayServerCdkeyPageRequestInfo info)
{
this.info = info;
}
}
public class RelayServerCdkeyPageRequestInfoFormatter : MemoryPackFormatter<RelayServerCdkeyPageRequestInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerCdkeyPageRequestInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerCdkeyPageRequestInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyPageRequestInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerCdkeyPageRequestInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerCdkeyPageResultInfo
{
[MemoryPackIgnore]
public readonly RelayServerCdkeyPageResultInfo info;
[MemoryPackInclude]
int Page => info.Page;
[MemoryPackInclude]
int Size => info.Size;
[MemoryPackInclude]
int Count => info.Count;
[MemoryPackInclude]
List<RelayServerCdkeyInfo> List => info.List;
[MemoryPackConstructor]
SerializableRelayServerCdkeyPageResultInfo(int page, int size, int count, List<RelayServerCdkeyInfo> list)
{
var info = new RelayServerCdkeyPageResultInfo
{
Count = count,
List = list,
Size = size,
Page = page
};
this.info = info;
}
public SerializableRelayServerCdkeyPageResultInfo(RelayServerCdkeyPageResultInfo info)
{
this.info = info;
}
}
public class RelayServerCdkeyPageResultInfoFormatter : MemoryPackFormatter<RelayServerCdkeyPageResultInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerCdkeyPageResultInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerCdkeyPageResultInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerCdkeyPageResultInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerCdkeyPageResultInfo>();
value = wrapped.info;
}
}
} }

View File

@@ -42,6 +42,7 @@ namespace linker.messenger.signin
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Host { get; set; } = string.Empty; public string Host { get; set; } = string.Empty;
public string SecretKey { get; set; } = string.Empty; public string SecretKey { get; set; } = string.Empty;
public string UserId { get; set; } = Guid.NewGuid().ToString();
} }

View File

@@ -45,6 +45,11 @@
/// <param name="secretKey"></param> /// <param name="secretKey"></param>
public void SetSecretKey(string secretKey); public void SetSecretKey(string secretKey);
/// <summary> /// <summary>
/// 设置用户id
/// </summary>
/// <param name="userid"></param>
public void SetUserId(string userid);
/// <summary>
/// 信标服务器 /// 信标服务器
/// </summary> /// </summary>
/// <param name="host"></param> /// <param name="host"></param>

View File

@@ -59,6 +59,7 @@ namespace linker.messenger.store.file
serviceCollection.AddSingleton<IRelayServerStore, RelayServerStore>(); serviceCollection.AddSingleton<IRelayServerStore, RelayServerStore>();
serviceCollection.AddSingleton<IRelayServerNodeStore, RelayServerNodeStore>(); serviceCollection.AddSingleton<IRelayServerNodeStore, RelayServerNodeStore>();
serviceCollection.AddSingleton<IRelayServerMasterStore, RelayServerMasterStore>(); serviceCollection.AddSingleton<IRelayServerMasterStore, RelayServerMasterStore>();
serviceCollection.AddSingleton<IRelayServerCdkeyStore, RelayServerCdkeyStore>();
serviceCollection.AddSingleton<ITunnelClientStore, TunnelClientStore>(); serviceCollection.AddSingleton<ITunnelClientStore, TunnelClientStore>();
@@ -66,6 +67,7 @@ namespace linker.messenger.store.file
serviceCollection.AddSingleton<ISignInClientStore, SignInClientStore>(); serviceCollection.AddSingleton<ISignInClientStore, SignInClientStore>();
serviceCollection.AddSingleton<ISignInServerStore, SignInServerStore>(); serviceCollection.AddSingleton<ISignInServerStore, SignInServerStore>();
serviceCollection.AddSingleton<SignInSyncSecretKey>(); serviceCollection.AddSingleton<SignInSyncSecretKey>();
serviceCollection.AddSingleton<SignInSyncUserId>();
serviceCollection.AddSingleton<SignInSyncServer>(); serviceCollection.AddSingleton<SignInSyncServer>();
serviceCollection.AddSingleton<SignInSyncGroupSecretKey>(); serviceCollection.AddSingleton<SignInSyncGroupSecretKey>();
@@ -107,6 +109,7 @@ namespace linker.messenger.store.file
SyncTreansfer syncTreansfer = serviceProvider.GetService<SyncTreansfer>(); SyncTreansfer syncTreansfer = serviceProvider.GetService<SyncTreansfer>();
syncTreansfer.AddSyncs(new List<ISync> { syncTreansfer.AddSyncs(new List<ISync> {
serviceProvider.GetService<SignInSyncSecretKey>(), serviceProvider.GetService<SignInSyncSecretKey>(),
serviceProvider.GetService<SignInSyncUserId>(),
serviceProvider.GetService<SignInSyncGroupSecretKey>(), serviceProvider.GetService<SignInSyncGroupSecretKey>(),
serviceProvider.GetService<SignInSyncServer>(), serviceProvider.GetService<SignInSyncServer>(),

View File

@@ -0,0 +1,65 @@
using linker.messenger.relay.server;
using LiteDB;
namespace linker.messenger.store.file.relay
{
public sealed class RelayServerCdkeyStore : IRelayServerCdkeyStore
{
private readonly Storefactory dBfactory;
private readonly ILiteCollection<RelayServerCdkeyInfo> liteCollection;
public RelayServerCdkeyStore(Storefactory dBfactory, FileConfig fileConfig)
{
this.dBfactory = dBfactory;
liteCollection = dBfactory.GetCollection<RelayServerCdkeyInfo>("relayCdkey");
}
public async Task<bool> Add(RelayServerCdkeyInfo info)
{
if (string.IsNullOrWhiteSpace(info.Id))
{
info.Id = ObjectId.NewObjectId().ToString();
liteCollection.Insert(info);
}
else
{
liteCollection.Update(info);
}
return await Task.FromResult(true);
}
public async Task<bool> Del(string id)
{
return await Task.FromResult(liteCollection.Delete(id));
}
public async Task<List<RelayServerCdkeyInfo>> Get(string userid)
{
return await Task.FromResult(liteCollection.Find(x => x.UserId == userid && x.LastBytes > 0 && x.StartTime <= DateTime.Now && x.EndTime < DateTime.Now).ToList());
}
public async Task<RelayServerCdkeyPageResultInfo> Get(RelayServerCdkeyPageRequestInfo relayServerCdkeyPageRequestInfo)
{
ILiteQueryable<RelayServerCdkeyInfo> query = liteCollection.Query();
if (string.IsNullOrWhiteSpace(relayServerCdkeyPageRequestInfo.Order) == false)
{
query = query.OrderBy(relayServerCdkeyPageRequestInfo.Order, relayServerCdkeyPageRequestInfo.Sort == "asc" ? Query.Ascending : Query.Descending);
}
if (string.IsNullOrWhiteSpace(relayServerCdkeyPageRequestInfo.UserId) == false)
{
query = query.Where(x => x.UserId == relayServerCdkeyPageRequestInfo.UserId);
}
if (string.IsNullOrWhiteSpace(relayServerCdkeyPageRequestInfo.Remark) == false)
{
query = query.Where(x => x.Remark.Contains(relayServerCdkeyPageRequestInfo.Remark));
}
return await Task.FromResult(new RelayServerCdkeyPageResultInfo
{
Page = relayServerCdkeyPageRequestInfo.Page,
Size = relayServerCdkeyPageRequestInfo.Size,
Count = query.Count(),
List = query.Skip((relayServerCdkeyPageRequestInfo.Page - 1) * relayServerCdkeyPageRequestInfo.Size).Limit(relayServerCdkeyPageRequestInfo.Size).ToList()
});
}
}
}

View File

@@ -43,6 +43,11 @@ namespace linker.messenger.store.file.signIn
Server.SecretKey = secretKey; Server.SecretKey = secretKey;
config.Data.Update(); config.Data.Update();
} }
public void SetUserId(string userid)
{
Server.UserId = userid;
config.Data.Update();
}
public void SetHost(string host) public void SetHost(string host)
{ {
Server.Host = host; Server.Host = host;

View File

@@ -46,6 +46,27 @@ namespace linker.messenger.store.file.signIn
signInClientStore.SetSecretKey(serializer.Deserialize<string>(data.Span)); signInClientStore.SetSecretKey(serializer.Deserialize<string>(data.Span));
} }
} }
public sealed class SignInSyncUserId : ISync
{
public string Name => "SignInUserId";
private readonly ISignInClientStore signInClientStore;
private readonly ISerializer serializer;
public SignInSyncUserId(ISignInClientStore signInClientStore, ISerializer serializer)
{
this.signInClientStore = signInClientStore;
this.serializer = serializer;
}
public Memory<byte> GetData()
{
return serializer.Serialize(signInClientStore.Server.UserId);
}
public void SetData(Memory<byte> data)
{
signInClientStore.SetUserId(serializer.Deserialize<string>(data.Span));
}
}
public sealed class SignInSyncGroupSecretKey : ISync public sealed class SignInSyncGroupSecretKey : ISync
{ {
public string Name => "GroupSecretKey"; public string Name => "GroupSecretKey";

View File

@@ -35,10 +35,13 @@ namespace linker.messenger.tunnel
{ {
LoggerHelper.Instance.Info($"tunnel route level getting."); LoggerHelper.Instance.Info($"tunnel route level getting.");
Info.RouteLevel = NetworkHelper.GetRouteLevel(signInClientStore.Server.Host, out List<IPAddress> ips); Info.RouteLevel = NetworkHelper.GetRouteLevel(signInClientStore.Server.Host, out List<IPAddress> ips);
Info.RouteIPs = ips.ToArray();
Info.LocalIPs = NetworkHelper.GetIPV6().Concat(NetworkHelper.GetIPV4()).ToArray();
LoggerHelper.Instance.Warning($"route ips:{string.Join(",", ips.Select(c => c.ToString()))}"); LoggerHelper.Instance.Warning($"route ips:{string.Join(",", ips.Select(c => c.ToString()))}");
LoggerHelper.Instance.Warning($"tunnel local ips :{string.Join(",", Info.LocalIPs.Select(c => c.ToString()))}"); Info.RouteIPs = ips.ToArray();
var ipv6 = NetworkHelper.GetIPV6();
LoggerHelper.Instance.Warning($"tunnel local ip6 :{string.Join(",", ipv6.Select(c => c.ToString()))}");
var ipv4 = NetworkHelper.GetIPV4();
LoggerHelper.Instance.Warning($"tunnel local ip4 :{string.Join(",", ipv4.Select(c => c.ToString()))}");
Info.LocalIPs = ipv6.Concat(ipv4).ToArray();
LoggerHelper.Instance.Warning($"tunnel route level:{Info.RouteLevel}"); LoggerHelper.Instance.Warning($"tunnel route level:{Info.RouteLevel}");
} }

View File

@@ -22,6 +22,8 @@ namespace linker.tun
private IPAddress address; private IPAddress address;
private byte prefixLength = 24; private byte prefixLength = 24;
private string defaultInterfaceName = string.Empty;
private CancellationTokenSource tokenSource; private CancellationTokenSource tokenSource;
public LinkerWinTunDevice() public LinkerWinTunDevice()
@@ -67,6 +69,7 @@ namespace linker.tun
AddIPV6(); AddIPV6();
GetWindowsInterfaceNum(); GetWindowsInterfaceNum();
GetDefaultInterface();
tokenSource = new CancellationTokenSource(); tokenSource = new CancellationTokenSource();
return true; return true;
@@ -115,7 +118,6 @@ namespace linker.tun
} }
} }
public void Shutdown() public void Shutdown()
{ {
tokenSource?.Cancel(); tokenSource?.Cancel();
@@ -137,7 +139,6 @@ namespace linker.tun
interfaceNumber = 0; interfaceNumber = 0;
} }
public void SetMtu(int value) public void SetMtu(int value)
{ {
CommandHelper.Windows(string.Empty, new string[] { CommandHelper.Windows(string.Empty, new string[] {
@@ -157,20 +158,18 @@ namespace linker.tun
IPAddress network = NetworkHelper.ToNetworkIP(this.address, NetworkHelper.ToPrefixValue(prefixLength)); IPAddress network = NetworkHelper.ToNetworkIP(this.address, NetworkHelper.ToPrefixValue(prefixLength));
CommandHelper.PowerShell($"New-NetNat -Name {Name} -InternalIPInterfaceAddressPrefix {network}/{prefixLength}", [], out error); CommandHelper.PowerShell($"New-NetNat -Name {Name} -InternalIPInterfaceAddressPrefix {network}/{prefixLength}", [], out error);
if (string.IsNullOrWhiteSpace(error))
{
return;
}
if (string.IsNullOrWhiteSpace(error) == false) CommandHelper.Windows(string.Empty, [$"net start SharedAccess"]);
string result = CommandHelper.Windows(string.Empty, [$"linker.ics.exe {defaultInterfaceName} {Name} enable"]);
if (result.Contains($"enable success"))
{ {
error = "NetNat Not Supported"; return;
string result = CommandHelper.Windows(string.Empty, ["netsh routing"]);
if (result.Contains("netsh routing ip"))
{
error = string.Empty;
}
else
{
error += "RRAS Not Supported";
}
} }
error = "NetNat and ICS not supported";
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -186,6 +185,7 @@ namespace linker.tun
{ {
CommandHelper.PowerShell($"start-service WinNat", [], out error); CommandHelper.PowerShell($"start-service WinNat", [], out error);
CommandHelper.PowerShell($"Remove-NetNat -Name {Name} -Confirm:$false", [], out error); CommandHelper.PowerShell($"Remove-NetNat -Name {Name} -Confirm:$false", [], out error);
CommandHelper.Windows(string.Empty, [$"linker.ics.exe {Name} disable"]);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -327,6 +327,33 @@ namespace linker.tun
} }
} }
private void GetDefaultInterface()
{
string[] lines = CommandHelper.Windows(string.Empty, new string[] { $"route print" }).Split(Environment.NewLine);
foreach (var item in lines)
{
if (item.Trim().StartsWith("0.0.0.0"))
{
string[] arr = Regex.Replace(item.Trim(), @"\s+", " ").Split(' ');
IPAddress ip = IPAddress.Parse(arr[arr.Length - 2]);
foreach (var inter in NetworkInterface.GetAllNetworkInterfaces())
{
try
{
if (ip.Equals(inter.GetIPProperties().UnicastAddresses.FirstOrDefault(c => c.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).Address))
{
defaultInterfaceName = inter.Name;
return;
}
}
catch (Exception)
{
}
}
}
}
}
private void ClearRegistry() private void ClearRegistry()
{ {

View File

@@ -9,3 +9,6 @@ export const setRelaySubscribe = () => {
export const relayConnect = (data) => { export const relayConnect = (data) => {
return sendWebsocketMsg('relay/Connect', data); return sendWebsocketMsg('relay/Connect', data);
} }
export const relayCdkeyAccess = () => {
return sendWebsocketMsg('relay/AccessCdkey');
}

View File

@@ -119,18 +119,34 @@ a.a-line {
padding-right: 1rem; padding-right: 1rem;
} }
.m-r-1 { .mgr-1 {
margin-right: 1rem; margin-right: 1rem !important;
} }
.m-l-1 { .mgl-1 {
margin-left: 1rem; margin-left: 1rem;
} }
.m-b-0 { .mgl-2 {
margin-left: 2rem;
}
.mgl-3 {
margin-left: 3rem;
}
.mgb-0 {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.mgb-3 {
margin-bottom: 3rem;
}
.mgt-3 {
margin-top: 3rem;
}
table { table {
border-spacing: 0; border-spacing: 0;
border-collapse: collapse; border-collapse: collapse;
@@ -174,6 +190,10 @@ span.split-pad10 {
color: #e68906 !important; color: #e68906 !important;
} }
.disable {
color: #ddd !important;
}
.gateway { .gateway {
&.green { &.green {
/* background: linear-gradient(270deg, #caff00, green, #0d6d23, #e38a00, green); */ /* background: linear-gradient(270deg, #caff00, green, #0d6d23, #e38a00, green); */

View File

@@ -7,6 +7,7 @@ export default {
'common.operating': 'In operation', 'common.operating': 'In operation',
'common.tips': 'Tips', 'common.tips': 'Tips',
'common.option': 'Option', 'common.option': 'Option',
'common.access': 'No access',
'head.home': 'Home', 'head.home': 'Home',
'head.server': 'Server', 'head.server': 'Server',
@@ -36,6 +37,8 @@ export default {
'server.messengerText': 'ip:port or domain:port', 'server.messengerText': 'ip:port or domain:port',
'server.messengerSecretKey': 'Messenger SecretKey', 'server.messengerSecretKey': 'Messenger SecretKey',
'server.messengerSecretKeyText': 'Messenger SecretKey', 'server.messengerSecretKeyText': 'Messenger SecretKey',
'server.messengerUserId': 'User Id',
'server.messengerUserIdText': 'Your unique identifier,for card key (CDKEY).',
'permission.closed': 'Closed', 'permission.closed': 'Closed',
'permission.simple': 'Simple', 'permission.simple': 'Simple',
@@ -120,6 +123,8 @@ export default {
'server.relayPublic': 'Public', 'server.relayPublic': 'Public',
'server.relayOper': 'Oper', 'server.relayOper': 'Oper',
'server.relayUse': 'Use', 'server.relayUse': 'Use',
'server.relayMyCdkey': 'My CDKEY',
'server.relayCdkey': 'Manager CDKEY',
'server.sforwardSecretKey': 'Server forward secretKey', 'server.sforwardSecretKey': 'Server forward secretKey',
'server.sforwardText': 'The server forward can be used when the key is correct', 'server.sforwardText': 'The server forward can be used when the key is correct',
@@ -148,5 +153,7 @@ export default {
'server.asyncRelaySecretKey': 'Relay secretKey', 'server.asyncRelaySecretKey': 'Relay secretKey',
'server.asyncSForwardSecretKey': 'Server forward secretKey', 'server.asyncSForwardSecretKey': 'Server forward secretKey',
'server.asyncUpdaterSecretKey': 'Update secretKey', 'server.asyncUpdaterSecretKey': 'Update secretKey',
'server.asyncTunnelTransports': 'Tunnel transports' 'server.asyncTunnelTransports': 'Tunnel transports',
'server.asyncSignInUserId': 'User Id',
} }

View File

@@ -7,6 +7,7 @@ export default {
'common.operating': '正在操作', 'common.operating': '正在操作',
'common.tips': '提示', 'common.tips': '提示',
'common.option': '选项', 'common.option': '选项',
'common.access': '无权限',
'head.home': '首页', 'head.home': '首页',
'head.server': '服务器', 'head.server': '服务器',
@@ -35,6 +36,8 @@ export default {
'server.messengerText': 'ip:端口 或者 域名:端口', 'server.messengerText': 'ip:端口 或者 域名:端口',
'server.messengerSecretKey': '信标密钥', 'server.messengerSecretKey': '信标密钥',
'server.messengerSecretKeyText': '密钥正确时可连接服务器', 'server.messengerSecretKeyText': '密钥正确时可连接服务器',
'server.messengerUserId': '用户id',
'server.messengerUserIdText': '你的唯一标识用于流量卡密CDKEY',
'permission.closed': '禁止通行', 'permission.closed': '禁止通行',
'permission.simple': '简单管理', 'permission.simple': '简单管理',
@@ -122,6 +125,8 @@ export default {
'server.relayPublic': '公开', 'server.relayPublic': '公开',
'server.relayOper': '操作', 'server.relayOper': '操作',
'server.relayUse': '使用', 'server.relayUse': '使用',
'server.relayMyCdkey': '我的CDKEY',
'server.relayCdkey': '管理CDKEY',
'server.sforwardSecretKey': '服务器穿透密钥', 'server.sforwardSecretKey': '服务器穿透密钥',
'server.sforwardText': '当密钥正确是可用', 'server.sforwardText': '当密钥正确是可用',
@@ -150,5 +155,6 @@ export default {
'server.asyncRelaySecretKey': '中继密钥', 'server.asyncRelaySecretKey': '中继密钥',
'server.asyncSForwardSecretKey': '服务器穿透密钥', 'server.asyncSForwardSecretKey': '服务器穿透密钥',
'server.asyncUpdaterSecretKey': '更新密钥', 'server.asyncUpdaterSecretKey': '更新密钥',
'server.asyncTunnelTransports': '打洞协议' 'server.asyncTunnelTransports': '打洞协议',
'server.asyncSignInUserId': '用户唯一标识',
} }

View File

@@ -6,7 +6,7 @@
接口 : <el-input v-model="state.api" style="width:70%"></el-input> 接口 : <el-input v-model="state.api" style="width:70%"></el-input>
</div> </div>
<div class="pdt-10"> <div class="pdt-10">
秘钥 : <el-input type="password" v-model="state.psd" style="width:70%"></el-input> 秘钥 : <el-input show-password type="password" v-model="state.psd" style="width:70%"></el-input>
</div> </div>
<div class="pdt-10"> <div class="pdt-10">
<el-checkbox v-model="state.save" >保存密码</el-checkbox> <el-checkbox v-model="state.save" >保存密码</el-checkbox>

View File

@@ -16,7 +16,7 @@
<el-checkbox v-model="state.ruleForm.Multicast" label="禁用广播" size="large" /> <el-checkbox v-model="state.ruleForm.Multicast" label="禁用广播" size="large" />
<el-checkbox v-model="state.ruleForm.Nat" label="禁用NAT" size="large" /> <el-checkbox v-model="state.ruleForm.Nat" label="禁用NAT" size="large" />
</el-form-item> </el-form-item>
<el-form-item prop="upgrade" class="m-b-0"> <el-form-item prop="upgrade" class="mgb-0">
<el-checkbox v-model="state.ruleForm.Upgrade" label="我很懂,我要使用高级功能(点对网和网对网)" size="large" /> <el-checkbox v-model="state.ruleForm.Upgrade" label="我很懂,我要使用高级功能(点对网和网对网)" size="large" />
</el-form-item> </el-form-item>
<div class="upgrade-wrap" v-if="state.ruleForm.Upgrade"> <div class="upgrade-wrap" v-if="state.ruleForm.Upgrade">

View File

@@ -34,13 +34,13 @@
<div> <div>
<template v-for="(item1,index) in tuntap.list[item.MachineId].Lans" :key="index"> <template v-for="(item1,index) in tuntap.list[item.MachineId].Lans" :key="index">
<template v-if="item1.Disabled"> <template v-if="item1.Disabled">
<div class="flex yellow" title="已禁用">{{ item1.IP }} / {{ item1.PrefixLength }}</div> <div class="flex disable" title="已禁用">{{ item1.IP }} / {{ item1.PrefixLength }}</div>
</template> </template>
<template v-else-if="item1.Exists"> <template v-else-if="item1.Exists">
<div class="flex yellow" title="与其它设备填写IP、或本机局域网IP有冲突">{{ item1.IP }} / {{ item1.PrefixLength }}</div> <div class="flex yellow" title="与其它设备填写IP、或本机局域网IP有冲突">{{ item1.IP }} / {{ item1.PrefixLength }}</div>
</template> </template>
<template v-else> <template v-else>
<div class="flex" title="正常使用" :class="{green:tuntap.list[item.MachineId].running}">{{ item1.IP }} / {{ item1.PrefixLength }}</div> <div class="flex green" title="正常使用">{{ item1.IP }} / {{ item1.PrefixLength }}</div>
</template> </template>
</template> </template>
</div> </div>

View File

@@ -32,6 +32,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="Oper" :label="$t('server.groupOper')" width="160"> <el-table-column prop="Oper" :label="$t('server.groupOper')" width="160">
<template #header>
<div class="flex">
<strong>{{ $t('server.groupOper') }}</strong><span class="flex-1"></span><Sync name="GroupSecretKey"></Sync>
</div>
</template>
<template #default="scope"> <template #default="scope">
<div> <div>
<el-popconfirm :title="$t('server.groupDelConfirm')" @confirm="handleDel(scope.$index)"> <el-popconfirm :title="$t('server.groupDelConfirm')" @confirm="handleDel(scope.$index)">
@@ -60,8 +65,9 @@ import { ElMessage } from 'element-plus';
import { computed, reactive, watch } from 'vue' import { computed, reactive, watch } from 'vue'
import { Delete,Plus,Select } from '@element-plus/icons-vue'; import { Delete,Plus,Select } from '@element-plus/icons-vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue'
export default { export default {
components:{Delete,Plus,Select }, components:{Delete,Plus,Select,Sync },
setup(props) { setup(props) {
const {t} = useI18n(); const {t} = useI18n();
const globalData = injectGlobalData(); const globalData = injectGlobalData();

View File

@@ -5,7 +5,7 @@
<div class="inner"> <div class="inner">
<div class="head flex"> <div class="head flex">
<div> <div>
<el-select v-model="state.type" @change="loadData" size="small" class="m-r-1" style="width: 6rem;"> <el-select v-model="state.type" @change="loadData" size="small" class="mgr-1" style="width: 6rem;">
<el-option :value="-1" label="all"></el-option> <el-option :value="-1" label="all"></el-option>
<el-option :value="0" label="debug"></el-option> <el-option :value="0" label="debug"></el-option>
<el-option :value="1" label="info"></el-option> <el-option :value="1" label="info"></el-option>

View File

@@ -1,88 +0,0 @@
<template>
<div :style="{ height: `${state.height}px` }">
<el-card shadow="never">
<template #header>{{ $t('server.asyncText') }}</template>
<div>
<el-checkbox v-model="state.checkAll" :indeterminate="state.isIndeterminate"
@change="handleCheckAllChange">{{
$t('server.asyncCheckAll') }}</el-checkbox>
<el-checkbox-group v-model="state.checkeds" @change="handleCheckedsChange">
<el-row>
<template v-for="name in state.names">
<el-col :span="8">
<el-checkbox :key="name.name" :label="name.label" :value="name.name">{{ name.label
}}</el-checkbox>
</el-col>
</template>
</el-row>
</el-checkbox-group>
</div>
<template #footer>
<div class="t-c">
<el-button type="success" @click="handleSync">{{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-card>
</div>
</template>
<script>
import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus';
import { computed, onMounted, reactive } from 'vue'
import { getSyncNames, setSync } from '@/apis/sync';
import { useI18n } from 'vue-i18n';
export default {
setup(props) {
const { t } = useI18n();
const globalData = injectGlobalData();
const state = reactive({
names: [],
checkAll: false,
isIndeterminate: false,
checkeds: [],
height: computed(() => globalData.value.height - 90),
});
const handleCheckAllChange = (val) => {
state.checkeds = val ? state.names.map(c => c.name) : [];
state.isIndeterminate = false
}
const handleCheckedsChange = (value) => {
const checkedCount = value.length
state.checkAll = checkedCount === state.names.length
state.isIndeterminate = checkedCount > 0 && checkedCount < state.names.length;
}
const labels = {
'SignInServer': t('server.asyncSignInServer'),
'SignInSecretKey': t('server.asyncSignInSecretKey'),
'GroupSecretKey': t('server.asyncGroupSecretKey'),
'RelaySecretKey': t('server.asyncRelaySecretKey'),
'SForwardSecretKey': t('server.asyncSForwardSecretKey'),
'UpdaterSecretKey': t('server.asyncUpdaterSecretKey'),
'TunnelTransports': t('server.asyncTunnelTransports')
}
onMounted(() => {
getSyncNames().then(res => {
state.names = res.map(c => {
return { name: c, label: labels[c] }
});
});
});
const handleSync = () => {
if (state.checkeds.length == 0) {
ElMessage.error(t('server.asyncSelect'));
return;
}
setSync(state.checkeds).then(res => {
ElMessage.success(t('common.oper'));
});
}
return { state, handleCheckAllChange, handleCheckedsChange, handleSync }
}
}
</script>
<style lang="stylus" scoped>
</style>

View File

@@ -1,32 +1,20 @@
<template> <template>
<div class="servers-wrap" > <div class="servers-wrap" >
<el-tabs type="border-card" style="width:100%" v-model="state.tab"> <SignInServers v-if="hasConfig"></SignInServers>
<el-tab-pane :label="$t('server.messenger')" name="signin" v-if="hasConfig">
<SignInServers></SignInServers>
</el-tab-pane>
<el-tab-pane :label="$t('server.sync')" name="async" v-if="hasSync">
<Async></Async>
</el-tab-pane>
</el-tabs>
</div> </div>
</template> </template>
<script> <script>
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import { injectGlobalData } from '@/provide'; import { injectGlobalData } from '@/provide';
import SignInServers from './SignInServers.vue'; import SignInServers from './SignInServers.vue';
import Async from './Async.vue';
export default { export default {
components:{SignInServers,Async}, components:{SignInServers},
setup(props) { setup(props) {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const hasConfig = computed(()=>globalData.value.hasAccess('Config')) const hasConfig = computed(()=>globalData.value.hasAccess('Config'))
const hasSync = computed(()=>globalData.value.hasAccess('Sync')); const state = reactive({});
const state = reactive({
tab:'signin'
});
return { return {
state,hasConfig,hasSync state,hasConfig
} }
} }
} }
@@ -38,9 +26,4 @@ export default {
color:#555; color:#555;
a{color:#333;} a{color:#333;}
} }
.el-checkbox{
vertical-align:middle;
margin-right:1rem;
}
</style> </style>

View File

@@ -1,11 +1,18 @@
<template> <template>
<el-form-item :label="$t('server.relaySecretKey')"> <el-form-item :label="$t('server.relaySecretKey')">
<el-input type="password" show-password v-model="state.list.SecretKey" maxlength="36" @change="handleSave" /> <div class="flex">
<el-checkbox v-model="state.list.SSL" :label="$t('server.relaySSL')" size="large" @change="handleSave" /> <el-input class="flex-1" type="password" show-password v-model="state.list.SecretKey" maxlength="36" @change="handleSave" />
<el-checkbox v-model="state.list.Disabled" :label="$t('server.relayDisable')" size="large" @change="handleSave" /> <Sync class="mgl-1" name="RelaySecretKey"></Sync>
<a href="javascript:;" @click="state.show=true" class="delay a-line" :class="{red:state.nodes.length==0,green:state.nodes.length>0}"> <div class="mgl-1">
<el-checkbox class="mgr-1" v-model="state.list.SSL" :label="$t('server.relaySSL')" @change="handleSave" />
<el-checkbox v-model="state.list.Disabled" :label="$t('server.relayDisable')" @change="handleSave" />
</div>
<a href="javascript:;" @click="state.show=true" class="mgl-1 delay a-line" :class="{red:state.nodes.length==0,green:state.nodes.length>0}">
{{$t('server.relayNodes')}} : {{state.nodes.length}} {{$t('server.relayNodes')}} : {{state.nodes.length}}
</a> </a>
<Relay></Relay>
</div>
</el-form-item> </el-form-item>
<el-dialog v-model="state.show" :title="$t('server.relayTitle')" width="760" top="2vh"> <el-dialog v-model="state.show" :title="$t('server.relayTitle')" width="760" top="2vh">
@@ -60,7 +67,10 @@ import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { onMounted, onUnmounted, reactive, watch } from 'vue' import { onMounted, onUnmounted, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue'
import Relay from './relay/Index.vue'
export default { export default {
components:{Sync,Relay},
setup(props) { setup(props) {
const {t} = useI18n(); const {t} = useI18n();
const globalData = injectGlobalData(); const globalData = injectGlobalData();
@@ -103,5 +113,4 @@ export default {
} }
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.delay{margin-left:3rem;}
</style> </style>

View File

@@ -2,7 +2,8 @@
<el-form-item :label="$t('server.sforwardSecretKey')"> <el-form-item :label="$t('server.sforwardSecretKey')">
<div class="flex"> <div class="flex">
<el-input class="flex-1" type="password" show-password v-model="state.SForwardSecretKey" maxlength="36" @blur="handleChange" /> <el-input class="flex-1" type="password" show-password v-model="state.SForwardSecretKey" maxlength="36" @blur="handleChange" />
<span>{{$t('server.sforwardText')}}</span> <Sync class="mgl-1" name="SForwardSecretKey"></Sync>
<span class="mgl-1">{{$t('server.sforwardText')}}</span>
</div> </div>
</el-form-item> </el-form-item>
</template> </template>
@@ -11,7 +12,9 @@ import { getSForwardSecretKey,setSForwardSecretKey } from '@/apis/sforward';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import {onMounted, reactive } from 'vue' import {onMounted, reactive } from 'vue'
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue'
export default { export default {
components:{Sync},
setup(props) { setup(props) {
const {t} = useI18n(); const {t} = useI18n();
const state = reactive({ const state = reactive({

View File

@@ -7,16 +7,25 @@
<el-form-item :label="$t('server.messengerAddr')"> <el-form-item :label="$t('server.messengerAddr')">
<div class="flex"> <div class="flex">
<el-input class="flex-1" v-model="state.list.Host" @change="handleSave" /> <el-input class="flex-1" v-model="state.list.Host" @change="handleSave" />
<span>{{$t('server.messengerText')}}</span> <Sync class="mgl-1" name="SignInServer"></Sync>
<span class="mgl-1">{{$t('server.messengerText')}}</span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="$t('server.messengerSecretKey')"> <el-form-item :label="$t('server.messengerSecretKey')">
<div class="flex"> <div class="flex">
<el-input class="flex-1" type="password" show-password maxlength="36" v-model="state.list.SecretKey" @change="handleSave" /> <el-input class="flex-1" type="password" show-password maxlength="36" v-model="state.list.SecretKey" @change="handleSave" />
<span>{{$t('server.messengerSecretKeyText')}}</span> <Sync class="mgl-1" name="SignInSecretKey"></Sync>
<span class="mgl-1">{{$t('server.messengerSecretKeyText')}}</span>
</div> </div>
</el-form-item> </el-form-item>
<RelayServers></RelayServers> <el-form-item :label="$t('server.messengerUserId')">
<div class="flex">
<el-input class="flex-1" type="password" show-password maxlength="36" v-model="state.list.UserId" @change="handleSave" />
<Sync class="mgl-1" name="SignInUserId"></Sync>
<span class="mgl-1">{{$t('server.messengerUserIdText')}}</span>
</div>
</el-form-item>
<RelayServers class="mgt-2"></RelayServers>
<SForward></SForward> <SForward></SForward>
<Updater></Updater> <Updater></Updater>
</el-form> </el-form>
@@ -38,8 +47,9 @@ import SForward from './SForward.vue';
import Updater from './Updater.vue'; import Updater from './Updater.vue';
import RelayServers from './RelayServers.vue'; import RelayServers from './RelayServers.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue'
export default { export default {
components:{SForward,Updater,RelayServers}, components:{SForward,Updater,RelayServers,Sync},
setup(props) { setup(props) {
const {t} = useI18n(); const {t} = useI18n();
const globalData = injectGlobalData(); const globalData = injectGlobalData();

View File

@@ -2,10 +2,11 @@
<el-form-item :label="$t('server.updaterSecretKey')"> <el-form-item :label="$t('server.updaterSecretKey')">
<div class="flex"> <div class="flex">
<el-input class="flex-1" type="password" show-password v-model="state.secretKey" maxlength="36" @blur="handleChange"/> <el-input class="flex-1" type="password" show-password v-model="state.secretKey" maxlength="36" @blur="handleChange"/>
<span>{{$t('server.updaterText')}}</span> <Sync class="mgl-1" name="UpdaterSecretKey"></Sync>
<span class="mgl-1">{{$t('server.updaterText')}}</span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="$t('server.updaterRate')"> <!-- <el-form-item :label="$t('server.updaterRate')">
<div> <div>
<div> <div>
<el-input-number v-model="state.year" :min="0" :max="99" style="width:12rem" @change="handleSecChange" /> {{$t('server.updaterY') }} <el-input-number v-model="state.year" :min="0" :max="99" style="width:12rem" @change="handleSecChange" /> {{$t('server.updaterY') }}
@@ -18,7 +19,7 @@
<el-input-number v-model="state.sec" :min="0" :max="99" style="width:12rem" @change="handleSecChange"/> {{$t('server.updaterS') }} <el-input-number v-model="state.sec" :min="0" :max="99" style="width:12rem" @change="handleSecChange"/> {{$t('server.updaterS') }}
</div> </div>
</div> </div>
</el-form-item> </el-form-item> -->
</template> </template>
<script> <script>
import { getSecretKey,setSecretKey, setUpdateInterval } from '@/apis/updater'; import { getSecretKey,setSecretKey, setUpdateInterval } from '@/apis/updater';
@@ -26,7 +27,9 @@ import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { onMounted, reactive } from 'vue' import { onMounted, reactive } from 'vue'
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue'
export default { export default {
components:{Sync},
setup(props) { setup(props) {
const {t} = useI18n(); const {t} = useI18n();
const globalData = injectGlobalData(); const globalData = injectGlobalData();

View File

@@ -0,0 +1,33 @@
<template>
<a href="javascript:;" class="mgl-1 a-line">{{$t('server.relayMyCdkey')}}</a>
<a v-if="state.showManager && hasRelayCdkey" href="javascript:;" class="mgl-1 a-line">{{$t('server.relayCdkey')}}</a>
</template>
<script>
import { relayCdkeyAccess } from '@/apis/relay';
import { injectGlobalData } from '@/provide';
import { computed, onMounted, reactive } from 'vue';
export default {
setup () {
const globalData = injectGlobalData();
const hasRelayCdkey = computed(()=>globalData.value.hasAccess('RelayCdkey'));
const state = reactive({
showManager:false
});
onMounted(()=>{
relayCdkeyAccess().then(res=>{
state.showManager = res;
}).catch(()=>{})
})
return {state,hasRelayCdkey}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -56,7 +56,6 @@ export default {
const hasExport = computed(()=>globalData.value.hasAccess('Export')); const hasExport = computed(()=>globalData.value.hasAccess('Export'));
const onlyNode = computed(()=>globalData.value.config.Client.OnlyNode); const onlyNode = computed(()=>globalData.value.config.Client.OnlyNode);
const machineId = computed(()=>globalData.value.config.Client.Id); const machineId = computed(()=>globalData.value.config.Client.Id);
console.log(globalData.value.config.Client);
const state = reactive({ const state = reactive({
show: false, show: false,
loading:false, loading:false,

View File

@@ -0,0 +1,45 @@
<template>
<div>
<el-button class="btn" size="small" @click="handleSync"><el-icon><Share /></el-icon></el-button>
</div>
</template>
<script>
import { setSync } from '@/apis/sync';
import { injectGlobalData } from '@/provide';
import {Share} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus';
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
export default {
props:['name'],
components: {Share},
setup (props) {
const { t } = useI18n();
const globalData = injectGlobalData();
const hasSync = computed(()=>globalData.value.hasAccess('Sync'));
const handleSync = ()=>{
if(!hasSync.value){
ElMessage.success(t('common.access'));
return;
}
ElMessageBox.confirm(`${t('server.sync')} ${t(`server.async${props.name}`)} ? `, t('common.tips'), {
confirmButtonText: t('common.confirm'),
cancelButtonText:t('common.cancel'),
type: 'warning'
}).then(() => {
setSync([props.name]).then(res => {
ElMessage.success(t('common.oper'));
});
}).catch(() => {});
}
return {
handleSync
}
}
}
</script>
<style lang="stylus" scoped>
</style>

View File

@@ -27,6 +27,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="Order" :label="$t('status.tunnelSort')" width="104" fixed="right"> <el-table-column prop="Order" :label="$t('status.tunnelSort')" width="104" fixed="right">
<template #header>
<div class="flex">
<strong>{{ $t('status.tunnelSort') }}</strong><span class="flex-1"></span><Sync name="TunnelTransports"></Sync>
</div>
</template>
<template #default="scope"> <template #default="scope">
<div> <div>
<el-input-number v-model="scope.row.Order" :min="1" :max="255" @change="handleOrderChange" size="small" /> <el-input-number v-model="scope.row.Order" :min="1" :max="255" @change="handleOrderChange" size="small" />
@@ -43,11 +48,12 @@ import { ElMessage } from 'element-plus';
import { computed,reactive, watch } from 'vue' import { computed,reactive, watch } from 'vue'
import { Delete,Plus,Top,Bottom } from '@element-plus/icons-vue'; import { Delete,Plus,Top,Bottom } from '@element-plus/icons-vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue'
export default { export default {
label:'打洞协议', label:'打洞协议',
name:'transports', name:'transports',
order:2, order:2,
components:{Delete,Plus,Top,Bottom}, components:{Delete,Plus,Top,Bottom,Sync},
setup(props) { setup(props) {
const {t} = useI18n(); const {t} = useI18n();
const globalData = injectGlobalData(); const globalData = injectGlobalData();

View File

@@ -20,9 +20,14 @@
<Title>linker</Title> <Title>linker</Title>
<Authors>snltty</Authors> <Authors>snltty</Authors>
<Company>snltty</Company> <Company>snltty</Company>
<Description>1. 修复litedb抢锁超时导致客户端登录失败问题 <Description>1. 优化linux下路由跟踪问题
2. 同步信标服务器 2. 优化linux下获取本机IP问题
3. 其它一些修复优化</Description> 3. 增加ICS让win7+、win server2008+支持NAT
4. 增加中继卡密
5. 增加内外穿透定时开关功能
6. 优化管理页面连接接口的体验
7. 优化一些UI体验去除同步页面将同步功能放置各个实际的位置
8. 其它一些修复优化</Description>
<Copyright>snltty</Copyright> <Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>

View File

@@ -1,5 +1,10 @@
v1.6.9 v1.6.9
2025-03-01 02:24:18 2025-03-02 17:55:03
1. 修复litedb抢锁超时导致客户端登录失败问题 1. 优化linux下路由跟踪问题
2. 同步信标服务器 2. 优化linux下获取本机IP问题
3. 其它一些修复优化 3. 增加ICS让win7+、win server2008+支持NAT
4. 增加中继卡密
5. 增加内外穿透定时开关功能
6. 优化管理页面连接接口的体验
7. 优化一些UI体验去除同步页面将同步功能放置各个实际的位置
8. 其它一些修复优化