using linker.tunnel.connection;
using linker.tunnel.transport;
using linker.libs;
using linker.libs.extends;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Net;
using linker.tunnel.wanport;
using System.Security.Cryptography.X509Certificates;
namespace linker.tunnel
{
public sealed class TunnelTransfer
{
///
/// 本机局域网IP,当然你也可以使用0.0.0.0,但是使用局域网IP会提高打洞成功率
///
public Func LocalIP { get; set; } = () => IPAddress.Any;
///
/// 服务器地址
///
public Func ServerHost { get; set; } = () => new IPEndPoint(IPAddress.Any, 0);
///
/// ssl加密证书,没有证书则无法加密通信
///
public Func Certificate { get; set; } = () => null;
///
/// 获取打洞协议列表
///
///
public Func> GetTunnelTransports { get; set; } = () => new List();
///
/// 保存打洞协议列表
///
///
public Action, bool> SetTunnelTransports { get; set; } = (list, update) => { };
///
/// 获取本地网络信息
///
///
public Func GetLocalConfig { get; set; } = () => new NetworkInfo();
///
/// 获取远端的外网信息,比如你是A,你要获取B的信息,可以在B调用 TunnelTransfer.GetWanPort() 发送回来
///
public Func> GetRemoteWanPort { get; set; } = async (info) => { return await Task.FromResult(new TunnelTransportWanPortInfo()); };
///
/// 发送开始打洞
///
public Func> SendConnectBegin { get; set; } = async (info) => { return await Task.FromResult(false); };
///
/// 发送打洞失败
///
public Func> SendConnectFail { get; set; } = async (info) => { return await Task.FromResult(false); };
///
/// 发送打洞成功
///
public Func> SendConnectSuccess { get; set; } = async (info) => { return await Task.FromResult(false); };
private List transports;
private TunnelWanPortTransfer tunnelWanPortTransfer;
private TunnelUpnpTransfer TunnelUpnpTransfer;
private ConcurrentDictionary connectingDic = new ConcurrentDictionary();
private uint flowid = 1;
private Dictionary>> OnConnected { get; } = new Dictionary>>();
public TunnelTransfer()
{
}
///
/// 加载打洞协议
///
///
public void LoadTransports(TunnelWanPortTransfer tunnelWanPortTransfer, TunnelUpnpTransfer TunnelUpnpTransfer, List transports)
{
this.tunnelWanPortTransfer = tunnelWanPortTransfer;
this.transports = transports;
this.TunnelUpnpTransfer = TunnelUpnpTransfer;
foreach (var item in transports)
{
item.OnSendConnectBegin = SendConnectBegin;
item.OnSendConnectFail = SendConnectFail;
item.OnSendConnectSuccess = SendConnectSuccess;
item.OnConnected = _OnConnected;
item.SetSSL(Certificate());
}
var transportItems = GetTunnelTransports();
//有新的协议
var newTransportNames = transports.Select(c => c.Name).Except(transportItems.Select(c => c.Name));
if (newTransportNames.Any())
{
transportItems.AddRange(transports.Where(c => newTransportNames.Contains(c.Name)).Select(c => new TunnelTransportItemInfo
{
Label = c.Label,
Name = c.Name,
ProtocolType = c.ProtocolType.ToString(),
Reverse = c.Reverse,
DisableReverse = c.DisableReverse,
SSL = c.SSL,
DisableSSL = c.DisableSSL,
Order = c.Order
}));
}
//有已移除的协议
var oldTransportNames = transportItems.Select(c => c.Name).Except(transports.Select(c => c.Name));
if (oldTransportNames.Any())
{
foreach (var item in transportItems.Where(c => oldTransportNames.Contains(c.Name)))
{
transportItems.Remove(item);
}
}
//强制更新一些信息
foreach (var item in transportItems)
{
var transport = transports.FirstOrDefault(c => c.Name == item.Name);
if (transport != null)
{
item.DisableReverse = transport.DisableReverse;
item.DisableSSL = transport.DisableSSL;
item.Name = transport.Name;
item.Label = transport.Label;
if (transport.DisableReverse)
{
item.Reverse = transport.Reverse;
}
if (transport.DisableSSL)
{
item.SSL = transport.SSL;
}
if (item.Order == 0)
{
item.Order = transport.Order;
}
}
}
SetTunnelTransports(transportItems, true);
LoggerHelper.Instance.Info($"load tunnel transport:{string.Join(",", transports.Select(c => c.Name))}");
}
///
/// 设置成功打洞回调
///
///
///
public void SetConnectedCallback(string transactionId, Action callback)
{
if (OnConnected.TryGetValue(transactionId, out List> callbacks) == false)
{
callbacks = new List>();
OnConnected[transactionId] = callbacks;
}
callbacks.Add(callback);
}
///
/// 移除打洞成功回调
///
///
///
public void RemoveConnectedCallback(string transactionId, Action callback)
{
if (OnConnected.TryGetValue(transactionId, out List> callbacks))
{
callbacks.Remove(callback);
}
}
///
/// 开始连接对方
///
/// 对方id
/// 事务id,随便起,你喜欢就好
/// 本次连接排除那些打洞协议
///
public async Task ConnectAsync(string remoteMachineId, string transactionId, TunnelProtocolType denyProtocols)
{
return await ConnectAsync(remoteMachineId, transactionId, transactionId, denyProtocols);
}
///
/// 开始连接对方
///
/// 对方id
/// 事务id,随便起,你喜欢就好
/// 事务tag,随便起,你喜欢就好
/// 本次连接排除那些打洞协议
///
public async Task ConnectAsync(string remoteMachineId, string transactionId, string transactionTag, TunnelProtocolType denyProtocols)
{
if (connectingDic.TryAdd(remoteMachineId, true) == false) return null;
try
{
foreach (TunnelTransportItemInfo transportItem in GetTunnelTransports().OrderBy(c => c.Order).Where(c => c.Disabled == false))
{
ITunnelTransport transport = transports.FirstOrDefault(c => c.Name == transportItem.Name);
//找不到这个打洞协议,或者是不支持的协议
if (transport == null || (transport.ProtocolType & denyProtocols) == transport.ProtocolType)
{
continue;
}
foreach (var wanPortProtocol in tunnelWanPortTransfer.Protocols)
{
//这个打洞协议不支持这个外网端口协议
if ((transport.AllowWanPortProtocolType & wanPortProtocol) != wanPortProtocol)
{
continue;
}
TunnelTransportInfo tunnelTransportInfo = null;
//是否在失败后尝试反向连接
int times = transportItem.Reverse ? 1 : 0;
for (int i = 0; i <= times; i++)
{
try
{
//获取自己的外网ip
Task localInfo = GetLocalInfo(wanPortProtocol);
//获取对方的外网ip
Task remoteInfo = GetRemoteWanPort(new TunnelWanPortProtocolInfo
{
MachineId = remoteMachineId,
ProtocolType = wanPortProtocol
});
await Task.WhenAll(localInfo, remoteInfo).ConfigureAwait(false);
if (localInfo.Result == null)
{
LoggerHelper.Instance.Error($"tunnel {transport.Name} get local external ip fail ");
break;
}
if (remoteInfo.Result == null)
{
LoggerHelper.Instance.Error($"tunnel {transport.Name} get remote {remoteMachineId} external ip fail ");
break;
}
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transport.Name} got local external ip {localInfo.Result.ToJson()}");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transport.Name} got remote external ip {remoteInfo.Result.ToJson()}");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transportItem.ToJson()}");
tunnelTransportInfo = new TunnelTransportInfo
{
Direction = (TunnelDirection)i,
TransactionId = transactionId,
TransactionTag = transactionTag,
TransportName = transport.Name,
TransportType = transport.ProtocolType,
Local = localInfo.Result,
Remote = remoteInfo.Result,
SSL = transportItem.SSL,
FlowId = Interlocked.Increment(ref flowid),
};
OnConnecting(tunnelTransportInfo);
ParseRemoteEndPoint(tunnelTransportInfo);
ITunnelConnection connection = await transport.ConnectAsync(tunnelTransportInfo).ConfigureAwait(false);
if (connection != null)
{
_OnConnected(connection);
return connection;
}
}
catch (Exception ex)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
}
}
if (tunnelTransportInfo != null)
{
OnConnectFail(tunnelTransportInfo);
}
}
}
}
catch (Exception ex)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
}
finally
{
connectingDic.TryRemove(remoteMachineId, out _);
}
return null;
}
///
/// 收到对方开始连接的消息
///
///
public void OnBegin(TunnelTransportInfo tunnelTransportInfo)
{
if (connectingDic.TryAdd(tunnelTransportInfo.Remote.MachineId, true) == false)
{
return;
}
try
{
ITunnelTransport _transports = transports.FirstOrDefault(c => c.Name == tunnelTransportInfo.TransportName && c.ProtocolType == tunnelTransportInfo.TransportType);
TunnelTransportItemInfo item = GetTunnelTransports().FirstOrDefault(c => c.Name == tunnelTransportInfo.TransportName && c.Disabled == false);
if (_transports != null && item != null)
{
OnConnectBegin(tunnelTransportInfo);
ParseRemoteEndPoint(tunnelTransportInfo);
_transports.OnBegin(tunnelTransportInfo).ContinueWith((result) =>
{
connectingDic.TryRemove(tunnelTransportInfo.Remote.MachineId, out _);
});
}
else
{
connectingDic.TryRemove(tunnelTransportInfo.Remote.MachineId, out _);
_ = SendConnectFail(tunnelTransportInfo);
}
}
catch (Exception ex)
{
connectingDic.TryRemove(tunnelTransportInfo.Remote.MachineId, out _);
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
}
}
///
/// 收到对方发来的连接失败的消息
///
///
public void OnFail(TunnelTransportInfo tunnelTransportInfo)
{
ITunnelTransport _transports = transports.FirstOrDefault(c => c.Name == tunnelTransportInfo.TransportName && c.ProtocolType == tunnelTransportInfo.TransportType);
_transports?.OnFail(tunnelTransportInfo);
}
///
/// 收到对方发来的连接成功的消息
///
///
public void OnSuccess(TunnelTransportInfo tunnelTransportInfo)
{
ITunnelTransport _transports = transports.FirstOrDefault(c => c.Name == tunnelTransportInfo.TransportName && c.ProtocolType == tunnelTransportInfo.TransportType);
_transports?.OnSuccess(tunnelTransportInfo);
}
///
/// 获取自己的外网IP,给别人调用
///
///
public async Task GetWanPort(TunnelWanPortProtocolInfo _info)
{
return await GetLocalInfo(_info.ProtocolType).ConfigureAwait(false);
}
///
/// 获取自己的外网IP
///
///
private async Task GetLocalInfo(TunnelWanPortProtocolType tunnelWanPortProtocolType)
{
TunnelWanPortEndPoint ip = await tunnelWanPortTransfer.GetWanPortAsync(ServerHost(), LocalIP(), tunnelWanPortProtocolType).ConfigureAwait(false);
if (ip != null)
{
MapInfo portMapInfo = TunnelUpnpTransfer.PortMap ?? new MapInfo { PrivatePort = 0, PublicPort = 0 };
var config = GetLocalConfig();
return new TunnelTransportWanPortInfo
{
Local = ip.Local,
Remote = ip.Remote,
LocalIps = config.LocalIps,
RouteLevel = config.RouteLevel,
MachineId = config.MachineId,
PortMapLan = portMapInfo.PrivatePort,
PortMapWan = portMapInfo.PublicPort,
};
}
return null;
}
private void OnConnecting(TunnelTransportInfo tunnelTransportInfo)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel connecting {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}");
}
private void OnConnectBegin(TunnelTransportInfo tunnelTransportInfo)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel connecting from {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}");
}
///
/// 连接成功
///
///
private void _OnConnected(ITunnelConnection connection)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Debug($"tunnel connect {connection.RemoteMachineId}->{connection.RemoteMachineName} success->{connection.IPEndPoint}");
//调用以下别人注册的回调
if (OnConnected.TryGetValue(Helper.GlobalString, out List> callbacks))
{
foreach (var item in callbacks)
{
item(connection);
}
}
if (OnConnected.TryGetValue(connection.TransactionId, out callbacks))
{
foreach (var item in callbacks)
{
item(connection);
}
}
}
private void OnConnectFail(TunnelTransportInfo tunnelTransportInfo)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"tunnel connect {tunnelTransportInfo.Remote.MachineId} fail");
}
private void ParseRemoteEndPoint(TunnelTransportInfo tunnelTransportInfo)
{
//要连接哪些IP
List eps = new List();
//先尝试内网ipv4
if (tunnelTransportInfo.Local.Remote.Address.Equals(tunnelTransportInfo.Remote.Remote.Address))
{
eps.AddRange(tunnelTransportInfo.Remote.LocalIps.Where(c => c.AddressFamily == AddressFamily.InterNetwork).SelectMany(c => new List
{
new IPEndPoint(c, tunnelTransportInfo.Remote.Local.Port),
new IPEndPoint(c, tunnelTransportInfo.Remote.Remote.Port),
new IPEndPoint(c, tunnelTransportInfo.Remote.Remote.Port + 1)
}));
}
//在尝试外网
eps.AddRange(new List{
new IPEndPoint(tunnelTransportInfo.Remote.Remote.Address,tunnelTransportInfo.Remote.Remote.Port),
new IPEndPoint(tunnelTransportInfo.Remote.Remote.Address,tunnelTransportInfo.Remote.Remote.Port+1)
});
//再尝试IPV6
eps.AddRange(tunnelTransportInfo.Remote.LocalIps.Where(c => c.AddressFamily == AddressFamily.InterNetworkV6).SelectMany(c => new List
{
new IPEndPoint(c, tunnelTransportInfo.Remote.Local.Port),
new IPEndPoint(c, tunnelTransportInfo.Remote.Remote.Port),
new IPEndPoint(c, tunnelTransportInfo.Remote.Remote.Port + 1)
}));
//本机有V6
bool hasV6 = tunnelTransportInfo.Local.LocalIps.Any(c => c.AddressFamily == AddressFamily.InterNetworkV6);
//本机的局域网ip和外网ip
List localLocalIps = tunnelTransportInfo.Local.LocalIps.Concat(new List { tunnelTransportInfo.Local.Remote.Address }).ToList();
eps = eps
//对方是V6,本机也得有V6
.Where(c => (c.AddressFamily == AddressFamily.InterNetworkV6 && hasV6) || c.AddressFamily == AddressFamily.InterNetwork)
//端口和本机端口一样,那不应该是换回地址
.Where(c => (c.Port == tunnelTransportInfo.Local.Local.Port && c.Address.Equals(IPAddress.Loopback)) == false)
//端口和本机端口一样。那不应该是本机的IP
.Where(c => (c.Port == tunnelTransportInfo.Local.Local.Port && localLocalIps.Any(d => d.Equals(c.Address))) == false)
.Where(c => c.Address.Equals(IPAddress.Any) == false && c.Port > 0)
.ToList();
tunnelTransportInfo.RemoteEndPoints = eps;
}
private ConcurrentDictionary backgroundDic = new ConcurrentDictionary();
///
/// 开始后台打洞
///
///
///
public void StartBackground(string remoteMachineId, string transactionId, TunnelProtocolType denyProtocols, Func stopCallback, Func resultCallback, int times = 10, int delay = 10000)
{
if (AddBackground(remoteMachineId, transactionId) == false)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"tunnel background {remoteMachineId}@{transactionId} already exists");
return;
}
TimerHelper.Async(async () =>
{
try
{
ITunnelConnection connection = null;
await Task.Delay(delay);
for (int i = 1; i <= times; i++)
{
if (stopCallback()) break;
connection = await ConnectAsync(remoteMachineId, transactionId, denyProtocols);
if (connection != null)
{
break;
}
await Task.Delay(i * 3000);
}
await resultCallback(connection);
}
catch (Exception)
{
}
finally
{
RemoveBackground(remoteMachineId, transactionId);
}
});
}
private bool AddBackground(string remoteMachineId, string transactionId)
{
return backgroundDic.TryAdd(GetBackgroundKey(remoteMachineId, transactionId), true);
}
private void RemoveBackground(string remoteMachineId, string transactionId)
{
backgroundDic.TryRemove(GetBackgroundKey(remoteMachineId, transactionId), out _);
}
///
/// 是否正在后台打洞
///
///
///
///
public bool IsBackground(string remoteMachineId, string transactionId)
{
return backgroundDic.ContainsKey(GetBackgroundKey(remoteMachineId, transactionId));
}
private string GetBackgroundKey(string remoteMachineId, string transactionId)
{
return $"{remoteMachineId}@{transactionId}";
}
}
}