mirror of
https://github.com/snltty/linker.git
synced 2025-12-19 01:46:46 +08:00
170
This commit is contained in:
@@ -153,9 +153,9 @@ action.json
|
|||||||
|
|
||||||
:::tip[v1.7.0+]
|
:::tip[v1.7.0+]
|
||||||
1. 如果你使用第三方程序启动linker,`仅配置文件不存在时初始化,后续不会覆盖`,可以传入参数进行初始化,`client.json`、`server.json`、`action.json`、`common.json`,
|
1. 如果你使用第三方程序启动linker,`仅配置文件不存在时初始化,后续不会覆盖`,可以传入参数进行初始化,`client.json`、`server.json`、`action.json`、`common.json`,
|
||||||
2. 像这样,不填写的字段将以默认值生成,json的双引号需要转义,json里不能有空格
|
2. 像这样,不填写的字段将以默认值生成,将json转为base64
|
||||||
```
|
```
|
||||||
linker.exe --config-client {\"a\":\"1\"} --config-server {\"a\":\"1\"} --config-action {\"a\":\"1\"} --config-common {\"a\":\"1\"}
|
linker.exe --config-client base641 --config-server base642 --config-action base643 --config-common base644
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ using linker.messenger.updater;
|
|||||||
using linker.messenger.store.file;
|
using linker.messenger.store.file;
|
||||||
using linker.messenger.serializer.memorypack;
|
using linker.messenger.serializer.memorypack;
|
||||||
using linker.libs;
|
using linker.libs;
|
||||||
using linker.libs.extends;
|
|
||||||
|
|
||||||
namespace linker.messenger.entry
|
namespace linker.messenger.entry
|
||||||
{
|
{
|
||||||
@@ -30,11 +29,6 @@ namespace linker.messenger.entry
|
|||||||
private static OperatingManager builded = new OperatingManager();
|
private static OperatingManager builded = new OperatingManager();
|
||||||
private static OperatingManager setuped = new OperatingManager();
|
private static OperatingManager setuped = new OperatingManager();
|
||||||
|
|
||||||
public static void InputJsonConfig(string str)
|
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(str.DeJson<ConfigClientInfo>().ToJsonFormat());
|
|
||||||
}
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始初始化
|
/// 开始初始化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace linker.messenger.listen
|
|||||||
{
|
{
|
||||||
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
|
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
|
||||||
Socket socket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
Socket socket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||||
socket.IPv6Only(localEndPoint.AddressFamily, false);
|
//socket.IPv6Only(localEndPoint.AddressFamily, false);
|
||||||
socket.Bind(localEndPoint);
|
socket.Bind(localEndPoint);
|
||||||
socket.Listen(int.MaxValue);
|
socket.Listen(int.MaxValue);
|
||||||
|
|
||||||
|
|||||||
@@ -329,4 +329,318 @@ namespace linker.messenger.relay.client.transport
|
|||||||
return new List<RelayServerNodeReportInfo170>();
|
return new List<RelayServerNodeReportInfo170>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class RelayClientTransportSelfHostUdp : IRelayClientTransport
|
||||||
|
{
|
||||||
|
public string Name => "LinkerUdp";
|
||||||
|
public RelayClientType Type => RelayClientType.Linker;
|
||||||
|
public TunnelProtocolType ProtocolType => TunnelProtocolType.Udp;
|
||||||
|
|
||||||
|
private readonly IMessengerSender messengerSender;
|
||||||
|
private readonly ISerializer serializer;
|
||||||
|
private readonly IRelayClientStore relayClientStore;
|
||||||
|
private readonly SignInClientState signInClientState;
|
||||||
|
private readonly IMessengerStore messengerStore;
|
||||||
|
|
||||||
|
public RelayClientTransportSelfHostUdp(IMessengerSender messengerSender, ISerializer serializer, IRelayClientStore relayClientStore, SignInClientState signInClientState, IMessengerStore messengerStore)
|
||||||
|
{
|
||||||
|
this.messengerSender = messengerSender;
|
||||||
|
this.serializer = serializer;
|
||||||
|
this.relayClientStore = relayClientStore;
|
||||||
|
this.signInClientState = signInClientState;
|
||||||
|
this.messengerStore = messengerStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ITunnelConnection> RelayAsync(RelayInfo170 relayInfo)
|
||||||
|
{
|
||||||
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//问一下能不能中继
|
||||||
|
RelayAskResultInfo170 relayAskResultInfo = await RelayAsk(relayInfo);
|
||||||
|
relayInfo.FlowingId = relayAskResultInfo.FlowingId;
|
||||||
|
if (relayInfo.FlowingId == 0 || relayAskResultInfo.Nodes.Count == 0)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
LoggerHelper.Instance.Error($"relay ask fail,flowid:{relayInfo.FlowingId},nodes:{relayAskResultInfo.Nodes.Count}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//测试一下延迟
|
||||||
|
if (relayAskResultInfo.Nodes.Count > 1)
|
||||||
|
{
|
||||||
|
//relayAskResultInfo.Nodes = await TestDelay(relayAskResultInfo.Nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//连接中继节点服务器
|
||||||
|
Socket socket = await ConnectNodeServer(relayInfo, relayAskResultInfo.Nodes);
|
||||||
|
if (socket == null)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
LoggerHelper.Instance.Error($"relay connect server fail,flowid:{relayInfo.FlowingId},nodes:{relayAskResultInfo.Nodes.Count}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//让对方确认中继
|
||||||
|
if (await RelayConfirm(relayInfo) == false)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
LoggerHelper.Instance.Error($"relay confirm fail,flowid:{relayInfo.FlowingId},nodes:{relayAskResultInfo.Nodes.Count}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//成功建立连接,
|
||||||
|
SslStream sslStream = null;
|
||||||
|
if (relayInfo.SSL)
|
||||||
|
{
|
||||||
|
sslStream = new SslStream(new NetworkStream(socket, false), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
|
||||||
|
await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
|
||||||
|
{
|
||||||
|
EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TunnelConnectionTcp
|
||||||
|
{
|
||||||
|
Direction = TunnelDirection.Forward,
|
||||||
|
ProtocolType = TunnelProtocolType.Tcp,
|
||||||
|
RemoteMachineId = relayInfo.RemoteMachineId,
|
||||||
|
RemoteMachineName = relayInfo.RemoteMachineName,
|
||||||
|
Stream = sslStream,
|
||||||
|
Socket = socket,
|
||||||
|
Mode = TunnelMode.Client,
|
||||||
|
IPEndPoint = NetworkHelper.TransEndpointFamily(socket.RemoteEndPoint as IPEndPoint),
|
||||||
|
TransactionId = relayInfo.TransactionId,
|
||||||
|
TransportName = Name,
|
||||||
|
Type = TunnelType.Relay,
|
||||||
|
NodeId = relayInfo.NodeId,
|
||||||
|
SSL = relayInfo.SSL,
|
||||||
|
BufferSize = 3
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
{
|
||||||
|
LoggerHelper.Instance.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RelayAskResultInfo170> RelayAsk(RelayInfo170 relayInfo)
|
||||||
|
{
|
||||||
|
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
|
||||||
|
{
|
||||||
|
Connection = signInClientState.Connection,
|
||||||
|
MessengerId = (ushort)RelayMessengerIds.RelayAsk170,
|
||||||
|
Payload = serializer.Serialize(relayInfo),
|
||||||
|
Timeout = 2000
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
if (resp.Code != MessageResponeCodes.OK)
|
||||||
|
{
|
||||||
|
return new RelayAskResultInfo170();
|
||||||
|
}
|
||||||
|
|
||||||
|
RelayAskResultInfo170 result = serializer.Deserialize<RelayAskResultInfo170>(resp.Data.Span);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
private async Task<Socket> ConnectNodeServer(RelayInfo170 relayInfo, List<RelayServerNodeReportInfo170> nodes)
|
||||||
|
{
|
||||||
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(1 * 1024);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var node in nodes.Where(c => c.Id == relayInfo.NodeId).Concat(nodes.Where(c => c.Id != relayInfo.NodeId)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IPEndPoint ep = node.EndPoint;
|
||||||
|
if (ep == null || ep.Address.Equals(IPAddress.Any))
|
||||||
|
{
|
||||||
|
ep = signInClientState.Connection.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
LoggerHelper.Instance.Debug($"connect relay server {ep}");
|
||||||
|
|
||||||
|
//连接中继服务器
|
||||||
|
Socket socket = new Socket(ep.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
|
||||||
|
socket.KeepAlive();
|
||||||
|
await socket.ConnectAsync(ep).WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//建立关联
|
||||||
|
RelayMessageInfo relayMessage = new RelayMessageInfo
|
||||||
|
{
|
||||||
|
FlowId = relayInfo.FlowingId,
|
||||||
|
Type = RelayMessengerType.Ask,
|
||||||
|
FromId = relayInfo.FromMachineId,
|
||||||
|
ToId = relayInfo.RemoteMachineId,
|
||||||
|
NodeId = node.Id,
|
||||||
|
};
|
||||||
|
await socket.SendAsync(new byte[] { (byte)ResolverType.Relay });
|
||||||
|
await socket.SendAsync(serializer.Serialize(relayMessage));
|
||||||
|
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"relay connected {ep}");
|
||||||
|
|
||||||
|
//是否允许连接
|
||||||
|
int length = await socket.ReceiveAsync(buffer.AsMemory(0, 1));
|
||||||
|
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
LoggerHelper.Instance.Debug($"relay connected {ep}->{buffer[0]}");
|
||||||
|
if (buffer[0] == 0)
|
||||||
|
{
|
||||||
|
relayInfo.Server = node.EndPoint;
|
||||||
|
relayInfo.NodeId = node.Id;
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
socket.SafeClose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
{
|
||||||
|
LoggerHelper.Instance.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
{
|
||||||
|
LoggerHelper.Instance.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
private async Task<bool> RelayConfirm(RelayInfo170 relayInfo)
|
||||||
|
{
|
||||||
|
//通知对方去确认中继
|
||||||
|
var resp = await messengerSender.SendReply(new MessageRequestWrap
|
||||||
|
{
|
||||||
|
Connection = signInClientState.Connection,
|
||||||
|
MessengerId = (ushort)RelayMessengerIds.RelayForward170,
|
||||||
|
Payload = serializer.Serialize(relayInfo),
|
||||||
|
});
|
||||||
|
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public async Task<bool> OnBeginAsync(RelayInfo170 relayInfo, Action<ITunnelConnection> callback)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IPEndPoint ep = relayInfo.Server == null || relayInfo.Server.Address.Equals(IPAddress.Any) ? signInClientState.Connection.Address : relayInfo.Server;
|
||||||
|
|
||||||
|
Socket socket = new Socket(ep.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
|
||||||
|
socket.KeepAlive();
|
||||||
|
await socket.ConnectAsync(ep).WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
RelayMessageInfo relayMessage = new RelayMessageInfo
|
||||||
|
{
|
||||||
|
FlowId = relayInfo.FlowingId,
|
||||||
|
Type = RelayMessengerType.Answer,
|
||||||
|
FromId = relayInfo.FromMachineId,
|
||||||
|
ToId = relayInfo.RemoteMachineId,
|
||||||
|
NodeId = relayInfo.NodeId,
|
||||||
|
};
|
||||||
|
await socket.SendAsync(new byte[] { (byte)ResolverType.Relay });
|
||||||
|
await socket.SendAsync(serializer.Serialize(relayMessage));
|
||||||
|
|
||||||
|
_ = WaitSSL(socket, relayInfo).ContinueWith((result) =>
|
||||||
|
{
|
||||||
|
callback(result.Result);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
{
|
||||||
|
LoggerHelper.Instance.Error(ex);
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TunnelConnectionTcp> WaitSSL(Socket socket, RelayInfo170 relayInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SslStream sslStream = null;
|
||||||
|
if (relayInfo.SSL)
|
||||||
|
{
|
||||||
|
sslStream = new SslStream(new NetworkStream(socket, false), false);
|
||||||
|
await sslStream.AuthenticateAsServerAsync(messengerStore.Certificate, false, SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
return new TunnelConnectionTcp
|
||||||
|
{
|
||||||
|
Direction = TunnelDirection.Reverse,
|
||||||
|
ProtocolType = TunnelProtocolType.Tcp,
|
||||||
|
RemoteMachineId = relayInfo.RemoteMachineId,
|
||||||
|
RemoteMachineName = relayInfo.RemoteMachineName,
|
||||||
|
Stream = sslStream,
|
||||||
|
Socket = socket,
|
||||||
|
Mode = TunnelMode.Server,
|
||||||
|
IPEndPoint = NetworkHelper.TransEndpointFamily(socket.RemoteEndPoint as IPEndPoint),
|
||||||
|
TransactionId = relayInfo.TransactionId,
|
||||||
|
TransportName = Name,
|
||||||
|
Type = TunnelType.Relay,
|
||||||
|
NodeId = relayInfo.NodeId,
|
||||||
|
SSL = relayInfo.SSL,
|
||||||
|
BufferSize = 3,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
{
|
||||||
|
LoggerHelper.Instance.Error(ex);
|
||||||
|
}
|
||||||
|
socket.SafeClose();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<RelayServerNodeReportInfo170>> RelayTestAsync(RelayTestInfo170 relayTestInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
|
||||||
|
{
|
||||||
|
Connection = signInClientState.Connection,
|
||||||
|
MessengerId = (ushort)RelayMessengerIds.RelayTest170,
|
||||||
|
Payload = serializer.Serialize(relayTestInfo),
|
||||||
|
Timeout = 2000
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (resp.Code == MessageResponeCodes.OK)
|
||||||
|
{
|
||||||
|
return serializer.Deserialize<List<RelayServerNodeReportInfo170>>(resp.Data.Span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return new List<RelayServerNodeReportInfo170>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,15 @@ namespace linker.messenger.relay.server
|
|||||||
{
|
{
|
||||||
return limitTotal.TryLimit(ref length);
|
return limitTotal.TryLimit(ref length);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 总限速
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool TryLimitPacket(int length)
|
||||||
|
{
|
||||||
|
return limitTotal.TryLimitPacket(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -342,7 +351,7 @@ namespace linker.messenger.relay.server
|
|||||||
MaxGbTotal = node.MaxGbTotal,
|
MaxGbTotal = node.MaxGbTotal,
|
||||||
MaxGbTotalLastBytes = node.MaxGbTotalLastBytes,
|
MaxGbTotalLastBytes = node.MaxGbTotalLastBytes,
|
||||||
MaxConnection = node.MaxConnection,
|
MaxConnection = node.MaxConnection,
|
||||||
ConnectionRatio = Math.Round(connectionNum / 2.0),
|
ConnectionRatio = connectionNum,
|
||||||
EndPoint = endPoint,
|
EndPoint = endPoint,
|
||||||
Url = node.Url
|
Url = node.Url
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using linker.libs.extends;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using linker.libs;
|
using linker.libs;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace linker.messenger.relay.server
|
namespace linker.messenger.relay.server
|
||||||
{
|
{
|
||||||
@@ -20,9 +21,12 @@ namespace linker.messenger.relay.server
|
|||||||
{
|
{
|
||||||
this.relayServerNodeTransfer = relayServerNodeTransfer;
|
this.relayServerNodeTransfer = relayServerNodeTransfer;
|
||||||
this.serializer = serializer;
|
this.serializer = serializer;
|
||||||
|
ClearTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ulong, RelayWrapInfo> relayDic = new ConcurrentDictionary<ulong, RelayWrapInfo>();
|
private readonly ConcurrentDictionary<ulong, TaskCompletionSource<Socket>> relayDic = new ConcurrentDictionary<ulong, TaskCompletionSource<Socket>>();
|
||||||
|
private readonly ConcurrentDictionary<IPEndPoint, RelayUdpNatInfo> udpNat = new ConcurrentDictionary<IPEndPoint, RelayUdpNatInfo>();
|
||||||
|
private readonly ConcurrentDictionary<ulong, RelayUdpNatInfo> relayUdpDic = new ConcurrentDictionary<ulong, RelayUdpNatInfo>();
|
||||||
|
|
||||||
public virtual void AddReceive(string key, string from, string to, string groupid, long bytes)
|
public virtual void AddReceive(string key, string from, string to, string groupid, long bytes)
|
||||||
{
|
{
|
||||||
@@ -40,15 +44,100 @@ namespace linker.messenger.relay.server
|
|||||||
|
|
||||||
public async Task Resolve(Socket socket, IPEndPoint ep, Memory<byte> memory)
|
public async Task Resolve(Socket socket, IPEndPoint ep, Memory<byte> memory)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
RelayUdpStep step = (RelayUdpStep)memory.Span[0];
|
||||||
|
memory = memory.Slice(1);
|
||||||
|
|
||||||
|
if (step == RelayUdpStep.Forward)
|
||||||
|
{
|
||||||
|
if (udpNat.TryGetValue(ep, out RelayUdpNatInfo natTarget) && natTarget.Target != null)
|
||||||
|
{
|
||||||
|
natTarget.LastTicks = Environment.TickCount64;
|
||||||
|
await CopyToAsync(natTarget, socket,ep,memory);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RelayMessageInfo relayMessage = serializer.Deserialize<RelayMessageInfo>(memory.Span);
|
||||||
|
|
||||||
|
//ask 是发起端来的,那key就是 发起端->目标端, answer的,目标和来源会交换,所以转换一下
|
||||||
|
string key = relayMessage.Type == RelayMessengerType.Ask ? $"{relayMessage.FromId}->{relayMessage.ToId}->{relayMessage.FlowId}" : $"{relayMessage.ToId}->{relayMessage.FromId}->{relayMessage.FlowId}";
|
||||||
|
//获取缓存
|
||||||
|
RelayCacheInfo relayCache = await relayServerNodeTransfer.TryGetRelayCache(key);
|
||||||
|
if (relayCache == null)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
LoggerHelper.Instance.Error($"relay {relayMessage.Type} get cache fail,flowid:{relayMessage.FlowId}");
|
||||||
|
await socket.SendToAsync(new byte[] { 1 }, ep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relayMessage.Type == RelayMessengerType.Ask && relayServerNodeTransfer.Validate(relayCache) == false)
|
||||||
|
{
|
||||||
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
|
LoggerHelper.Instance.Error($"relay {relayMessage.Type} Validate false,flowid:{relayMessage.FlowId}");
|
||||||
|
await socket.SendToAsync(new byte[] { 1 }, ep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//流量统计
|
||||||
|
AddReceive(relayCache.FromId, relayCache.FromName, relayCache.ToName, relayCache.GroupId, memory.Length);
|
||||||
|
|
||||||
|
//回应
|
||||||
|
if (relayMessage.Type == RelayMessengerType.Answer)
|
||||||
|
{
|
||||||
|
if (relayUdpDic.TryRemove(relayCache.FlowId, out RelayUdpNatInfo natAsk))
|
||||||
|
{
|
||||||
|
natAsk.Target = ep;
|
||||||
|
|
||||||
|
RelayUdpNatInfo natAnswer = new RelayUdpNatInfo { Target = natAsk.Source, Traffic = natAsk.Traffic, Source = ep };
|
||||||
|
udpNat.AddOrUpdate(ep, natAnswer, (a, b) => natAnswer);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//请求
|
||||||
|
RelayTrafficCacheInfo trafficCacheInfo = new RelayTrafficCacheInfo { Cache = relayCache, Sendt = 0, Limit = new RelaySpeedLimit() };
|
||||||
|
RelayUdpNatInfo nat = new RelayUdpNatInfo { IsAsk = true, Source = ep, Traffic = trafficCacheInfo };
|
||||||
|
udpNat.AddOrUpdate(ep, nat, (a, b) => nat);
|
||||||
|
relayUdpDic.TryAdd(relayCache.FlowId, nat);
|
||||||
|
|
||||||
|
relayServerNodeTransfer.AddTrafficCache(trafficCacheInfo);
|
||||||
|
relayServerNodeTransfer.IncrementConnectionNum();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
private async Task CopyToAsync(RelayUdpNatInfo nat, Socket socket, IPEndPoint ep, Memory<byte> memory)
|
||||||
|
{
|
||||||
|
RelayTrafficCacheInfo trafficCacheInfo = nat.Traffic;
|
||||||
|
int bytesRead = memory.Length;
|
||||||
|
|
||||||
|
//流量限制
|
||||||
|
if (relayServerNodeTransfer.AddBytes(trafficCacheInfo, bytesRead) == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//总速度
|
||||||
|
if (relayServerNodeTransfer.NeedLimit(trafficCacheInfo) && relayServerNodeTransfer.TryLimitPacket(bytesRead) == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//单个速度
|
||||||
|
if (trafficCacheInfo.Limit.NeedLimit() && trafficCacheInfo.Limit.TryLimitPacket(bytesRead) == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AddReceive(trafficCacheInfo.Cache.FromId, trafficCacheInfo.Cache.FromName, trafficCacheInfo.Cache.ToName, trafficCacheInfo.Cache.GroupId, bytesRead);
|
||||||
|
AddSendt(trafficCacheInfo.Cache.FromId, trafficCacheInfo.Cache.FromName, trafficCacheInfo.Cache.ToName, trafficCacheInfo.Cache.GroupId, bytesRead);
|
||||||
|
await socket.SendToAsync(memory, nat.Target).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task Resolve(Socket socket, Memory<byte> memory)
|
public async Task Resolve(Socket socket, Memory<byte> memory)
|
||||||
{
|
{
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024);
|
byte[] buffer1 = new byte[8 * 1024];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int length = await socket.ReceiveAsync(buffer.AsMemory(), SocketFlags.None).ConfigureAwait(false);
|
int length = await socket.ReceiveAsync(buffer1.AsMemory(), SocketFlags.None).ConfigureAwait(false);
|
||||||
RelayMessageInfo relayMessage = serializer.Deserialize<RelayMessageInfo>(buffer.AsMemory(0, length).Span);
|
RelayMessageInfo relayMessage = serializer.Deserialize<RelayMessageInfo>(buffer1.AsMemory(0, length).Span);
|
||||||
|
|
||||||
//ask 是发起端来的,那key就是 发起端->目标端, answer的,目标和来源会交换,所以转换一下
|
//ask 是发起端来的,那key就是 发起端->目标端, answer的,目标和来源会交换,所以转换一下
|
||||||
string key = relayMessage.Type == RelayMessengerType.Ask ? $"{relayMessage.FromId}->{relayMessage.ToId}->{relayMessage.FlowId}" : $"{relayMessage.ToId}->{relayMessage.FromId}->{relayMessage.FlowId}";
|
string key = relayMessage.Type == RelayMessengerType.Ask ? $"{relayMessage.FromId}->{relayMessage.ToId}->{relayMessage.FlowId}" : $"{relayMessage.ToId}->{relayMessage.FromId}->{relayMessage.FlowId}";
|
||||||
@@ -66,7 +155,7 @@ namespace linker.messenger.relay.server
|
|||||||
if (relayMessage.Type == RelayMessengerType.Ask && relayServerNodeTransfer.Validate(relayCache) == false)
|
if (relayMessage.Type == RelayMessengerType.Ask && relayServerNodeTransfer.Validate(relayCache) == false)
|
||||||
{
|
{
|
||||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
LoggerHelper.Instance.Error($"relay {relayMessage.Type} Validate false,flowid:{relayMessage.FlowId}");
|
LoggerHelper.Instance.Error($"relay {relayMessage.Type} validate false,flowid:{relayMessage.FlowId}");
|
||||||
await socket.SendAsync(new byte[] { 1 });
|
await socket.SendAsync(new byte[] { 1 });
|
||||||
socket.SafeClose();
|
socket.SafeClose();
|
||||||
return;
|
return;
|
||||||
@@ -74,61 +163,42 @@ namespace linker.messenger.relay.server
|
|||||||
|
|
||||||
//流量统计
|
//流量统计
|
||||||
AddReceive(relayCache.FromId, relayCache.FromName, relayCache.ToName, relayCache.GroupId, length);
|
AddReceive(relayCache.FromId, relayCache.FromName, relayCache.ToName, relayCache.GroupId, length);
|
||||||
|
|
||||||
|
if (relayMessage.Type == RelayMessengerType.Answer)
|
||||||
|
{
|
||||||
|
if (relayDic.TryRemove(relayCache.FlowId, out TaskCompletionSource<Socket> tcsAsk))
|
||||||
|
{
|
||||||
|
tcsAsk.SetResult(socket);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
socket.SafeClose();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (relayMessage.Type)
|
await socket.SendAsync(new byte[] { 0 });
|
||||||
{
|
|
||||||
case RelayMessengerType.Ask:
|
|
||||||
{
|
|
||||||
//添加本地缓存
|
|
||||||
RelayWrapInfo relayWrap = new RelayWrapInfo { Socket = socket, Tcs = new TaskCompletionSource() };
|
|
||||||
|
|
||||||
relayDic.TryAdd(relayCache.FlowId, relayWrap);
|
TaskCompletionSource<Socket> tcs = new TaskCompletionSource<Socket>();
|
||||||
|
relayDic.TryAdd(relayCache.FlowId, tcs);
|
||||||
|
Socket answerSocket = await tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(15000));
|
||||||
|
|
||||||
await socket.SendAsync(new byte[] { 0 });
|
byte[] buffer2 = new byte[8 * 1024];
|
||||||
//等待对方连接
|
RelayTrafficCacheInfo trafficCacheInfo = new RelayTrafficCacheInfo { Cache = relayCache, Sendt = 0, Limit = new RelaySpeedLimit() };
|
||||||
await relayWrap.Tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(15000));
|
relayServerNodeTransfer.AddTrafficCache(trafficCacheInfo);
|
||||||
}
|
relayServerNodeTransfer.IncrementConnectionNum();
|
||||||
break;
|
await Task.WhenAll(CopyToAsync(trafficCacheInfo, socket, answerSocket, buffer1), CopyToAsync(trafficCacheInfo, answerSocket, socket, buffer2)).ConfigureAwait(false);
|
||||||
case RelayMessengerType.Answer:
|
relayServerNodeTransfer.DecrementConnectionNum();
|
||||||
{
|
relayServerNodeTransfer.RemoveTrafficCache(trafficCacheInfo);
|
||||||
//看发起端缓存
|
|
||||||
if (relayDic.TryRemove(relayCache.FlowId, out RelayWrapInfo relayWrap) == false || relayWrap.Socket == null)
|
|
||||||
{
|
|
||||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
|
||||||
LoggerHelper.Instance.Error($"relay {relayMessage.Type} get cache fail,flowid:{relayMessage.FlowId}");
|
|
||||||
socket.SafeClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
relayWrap.Tcs.SetResult();
|
|
||||||
|
|
||||||
RelayTrafficCacheInfo trafficCacheInfo = new RelayTrafficCacheInfo { Cache = relayCache, Sendt = 0, Limit = new RelaySpeedLimit() };
|
|
||||||
relayServerNodeTransfer.AddTrafficCache(trafficCacheInfo);
|
|
||||||
relayServerNodeTransfer.IncrementConnectionNum();
|
|
||||||
_ = Task.WhenAll(CopyToAsync(trafficCacheInfo, socket, relayWrap.Socket), CopyToAsync(trafficCacheInfo, relayWrap.Socket, socket)).ContinueWith((result) =>
|
|
||||||
{
|
|
||||||
relayServerNodeTransfer.DecrementConnectionNum();
|
|
||||||
relayServerNodeTransfer.RemoveTrafficCache(trafficCacheInfo);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
|
||||||
LoggerHelper.Instance.Error($"relay {relayMessage.Type} unknow type,flowid:{relayMessage.FlowId}");
|
|
||||||
socket.SafeClose();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||||
LoggerHelper.Instance.Error($"{ex},flowid:{relayMessage.FlowId}");
|
LoggerHelper.Instance.Error($"{ex},flowid:{relayMessage.FlowId}");
|
||||||
if (relayDic.TryRemove(relayCache.FlowId, out RelayWrapInfo remove))
|
relayDic.TryRemove(relayCache.FlowId, out _);
|
||||||
{
|
socket.SafeClose();
|
||||||
remove.Socket?.SafeClose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -137,18 +207,13 @@ namespace linker.messenger.relay.server
|
|||||||
LoggerHelper.Instance.Error(ex);
|
LoggerHelper.Instance.Error(ex);
|
||||||
socket.SafeClose();
|
socket.SafeClose();
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private async Task CopyToAsync(RelayTrafficCacheInfo trafficCacheInfo, Socket source, Socket destination)
|
private async Task CopyToAsync(RelayTrafficCacheInfo trafficCacheInfo, Socket source, Socket destination, Memory<byte> memory)
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[8 * 1024];
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
while ((bytesRead = await source.ReceiveAsync(buffer.AsMemory(), SocketFlags.None).ConfigureAwait(false)) != 0)
|
while ((bytesRead = await source.ReceiveAsync(memory, SocketFlags.None).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
//流量限制
|
//流量限制
|
||||||
if (relayServerNodeTransfer.AddBytes(trafficCacheInfo, bytesRead) == false)
|
if (relayServerNodeTransfer.AddBytes(trafficCacheInfo, bytesRead) == false)
|
||||||
@@ -182,7 +247,7 @@ namespace linker.messenger.relay.server
|
|||||||
|
|
||||||
AddReceive(trafficCacheInfo.Cache.FromId, trafficCacheInfo.Cache.FromName, trafficCacheInfo.Cache.ToName, trafficCacheInfo.Cache.GroupId, bytesRead);
|
AddReceive(trafficCacheInfo.Cache.FromId, trafficCacheInfo.Cache.FromName, trafficCacheInfo.Cache.ToName, trafficCacheInfo.Cache.GroupId, bytesRead);
|
||||||
AddSendt(trafficCacheInfo.Cache.FromId, trafficCacheInfo.Cache.FromName, trafficCacheInfo.Cache.ToName, trafficCacheInfo.Cache.GroupId, bytesRead);
|
AddSendt(trafficCacheInfo.Cache.FromId, trafficCacheInfo.Cache.FromName, trafficCacheInfo.Cache.ToName, trafficCacheInfo.Cache.GroupId, bytesRead);
|
||||||
await destination.SendAsync(buffer.AsMemory(0, bytesRead), SocketFlags.None).ConfigureAwait(false);
|
await destination.SendAsync(memory.Slice(0, bytesRead), SocketFlags.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -194,6 +259,47 @@ namespace linker.messenger.relay.server
|
|||||||
destination.SafeClose();
|
destination.SafeClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearTask()
|
||||||
|
{
|
||||||
|
TimerHelper.SetIntervalLong(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long ticks = Environment.TickCount64;
|
||||||
|
foreach (var item in udpNat.Values.Where(c => c.IsAsk && ticks - c.LastTicks > 30000).ToList())
|
||||||
|
{
|
||||||
|
relayServerNodeTransfer.DecrementConnectionNum();
|
||||||
|
relayServerNodeTransfer.RemoveTrafficCache(item.Traffic);
|
||||||
|
|
||||||
|
relayUdpDic.TryRemove(item.Traffic.Cache.FlowId, out _);
|
||||||
|
if (item.Target != null)
|
||||||
|
{
|
||||||
|
udpNat.TryRemove(item.Target, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RelayUdpStep : byte
|
||||||
|
{
|
||||||
|
Connect = 0,
|
||||||
|
Forward = 1,
|
||||||
|
}
|
||||||
|
public sealed class RelayUdpNatInfo
|
||||||
|
{
|
||||||
|
public bool IsAsk { get; set; }
|
||||||
|
public IPEndPoint Source { get; set; }
|
||||||
|
public IPEndPoint Target { get; set; }
|
||||||
|
public long LastTicks { get; set; } = Environment.TickCount64;
|
||||||
|
public RelayTrafficCacheInfo Traffic { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -221,7 +327,7 @@ namespace linker.messenger.relay.server
|
|||||||
}
|
}
|
||||||
public bool TryLimit(ref int length)
|
public bool TryLimit(ref int length)
|
||||||
{
|
{
|
||||||
if (relayLimit == 0) return false;
|
if (relayLimit == 0) return true;
|
||||||
|
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
@@ -244,6 +350,26 @@ namespace linker.messenger.relay.server
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public bool TryLimitPacket(int length)
|
||||||
|
{
|
||||||
|
if (relayLimit == 0) return true;
|
||||||
|
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
long _relayLimitTicks = Environment.TickCount64;
|
||||||
|
long relayLimitTicksTemp = _relayLimitTicks - relayLimitTicks;
|
||||||
|
relayLimitTicks = _relayLimitTicks;
|
||||||
|
relayLimitBucket += relayLimitTicksTemp * relayLimitToken;
|
||||||
|
if (relayLimitBucket > relayLimit) relayLimitBucket = relayLimit;
|
||||||
|
|
||||||
|
if (relayLimitBucket >= length)
|
||||||
|
{
|
||||||
|
relayLimitBucket -= length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed partial class RelayCacheInfo
|
public sealed partial class RelayCacheInfo
|
||||||
@@ -279,13 +405,6 @@ namespace linker.messenger.relay.server
|
|||||||
public long LastBytes { get; set; }
|
public long LastBytes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public sealed class RelayWrapInfo
|
|
||||||
{
|
|
||||||
public TaskCompletionSource Tcs { get; set; }
|
|
||||||
public Socket Socket { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed partial class RelayMessageInfo
|
public sealed partial class RelayMessageInfo
|
||||||
{
|
{
|
||||||
public RelayMessengerType Type { get; set; }
|
public RelayMessengerType Type { get; set; }
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ namespace linker.messenger.store.file
|
|||||||
{
|
{
|
||||||
text = File.ReadAllText(item.Value.Path, encoding: System.Text.Encoding.UTF8);
|
text = File.ReadAllText(item.Value.Path, encoding: System.Text.Encoding.UTF8);
|
||||||
}
|
}
|
||||||
else if (dic != null && dic.ContainsKey(item.Value.Property.Name))
|
else if (dic != null && dic.TryGetValue(item.Value.Property.Name, out string base64))
|
||||||
{
|
{
|
||||||
text = dic[item.Value.Property.Name];
|
text = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -162,13 +162,14 @@ namespace linker.tun
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
CommandHelper.Windows(string.Empty, [$"net start SharedAccess"]);
|
CommandHelper.Windows(string.Empty, [$"net start SharedAccess"]);
|
||||||
string result = CommandHelper.Windows(string.Empty, [$"linker.ics.exe {defaultInterfaceName} {Name} enable"]);
|
string result = CommandHelper.Windows(string.Empty, [$"linker.ics.exe {defaultInterfaceName} {Name} enable"]);
|
||||||
if (result.Contains($"enable success"))
|
if (result.Contains($"enable success"))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
error = "NetNat and ICS not supported";
|
error = "NetNat and ICS not supported";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -185,7 +186,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"]);
|
//CommandHelper.Windows(string.Empty, [$"linker.ics.exe {Name} disable"]);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace linker
|
|||||||
Dictionary<string, string> configDic = new Dictionary<string, string>();
|
Dictionary<string, string> configDic = new Dictionary<string, string>();
|
||||||
for (int i = 0; i < args.Length; i++)
|
for (int i = 0; i < args.Length; i++)
|
||||||
{
|
{
|
||||||
if (args[i] == "--config-cient")
|
if (args[i] == "--config-client")
|
||||||
{
|
{
|
||||||
configDic.Add("Client", args[i+1]);
|
configDic.Add("Client", args[i+1]);
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
v1.7.0
|
v1.7.0
|
||||||
2025-03-13 09:36:12
|
2025-03-14 21:19:03
|
||||||
1. 优化linux下路由跟踪问题,提高启动速度
|
1. 优化linux下路由跟踪问题,提高启动速度
|
||||||
2. 优化linux下获取本机IP问题,提升虚拟网卡稳定性
|
2. 优化linux下获取本机IP问题,提升虚拟网卡稳定性
|
||||||
3. 增加ICS,让win7+、win server2008+支持NAT
|
3. 增加ICS,让win7+、win server2008+支持NAT
|
||||||
|
|||||||
Reference in New Issue
Block a user