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}"; } } }