using cmonitor.client; using cmonitor.config; using cmonitor.plugins.tunnel.compact; using cmonitor.plugins.tunnel.messenger; using cmonitor.plugins.tunnel.transport; using cmonitor.server; using common.libs; using MemoryPack; using Microsoft.Extensions.DependencyInjection; using System.Net.Sockets; using System.Reflection; using System.Transactions; namespace cmonitor.plugins.tunnel { public sealed class TunnelTransfer { private List transports; private readonly Config config; private readonly ServiceProvider serviceProvider; private readonly ClientSignInState clientSignInState; private readonly MessengerSender messengerSender; private readonly CompactTransfer compactTransfer; public Action OnConnected { get; set; } = (state) => { }; public TunnelTransfer(Config config, ServiceProvider serviceProvider, ClientSignInState clientSignInState, MessengerSender messengerSender, CompactTransfer compactTransfer) { this.config = config; this.serviceProvider = serviceProvider; this.clientSignInState = clientSignInState; this.messengerSender = messengerSender; this.compactTransfer = compactTransfer; } public void Load(Assembly[] assembs) { IEnumerable types = ReflectionHelper.GetInterfaceSchieves(assembs, typeof(ITransport)); types = config.Data.Common.PluginContains(types); transports = types.Select(c => (ITransport)serviceProvider.GetService(c)).Where(c => c != null).Where(c => string.IsNullOrWhiteSpace(c.Name) == false).ToList(); foreach (var item in transports) { item.OnSendConnectBegin = OnSendConnectBegin; item.OnSendConnectFail = OnSendConnectFail; item.OnConnectBegin = OnConnectBegin; item.OnConnecting = OnConnecting; item.OnConnected = _OnConnected; item.OnConnected += OnConnected; item.OnDisConnected = OnDisConnected; item.OnConnectFail = OnConnectFail; } Logger.Instance.Warning($"load tunnel transport:{string.Join(",", transports.Select(c => c.Name))}"); } public async Task ConnectAsync(string remoteMachineName, string transactionId) { IEnumerable _transports = transports.OrderBy(c => c.Type); foreach (ITransport transport in _transports) { //获取自己的外网ip TunnelTransportExternalIPInfo localInfo = await GetLocalInfo(transport.Type); if (localInfo == null) { continue; } //获取对方的外网ip TunnelTransportExternalIPInfo remoteInfo = await GetRemoteInfo(remoteMachineName, transport.Type); if (remoteInfo == null) { continue; } TunnelTransportInfo tunnelTransportInfo = new TunnelTransportInfo { Direction = TunnelTransportDirection.Forward, TransactionId = transactionId, TransportName = transport.Name, TransportType = transport.Type, Local = localInfo, Remote = remoteInfo, }; TunnelTransportState state = await transport.ConnectAsync(tunnelTransportInfo); if (state != null) { _OnConnected(state); return state; } } return null; } public void OnBegin(TunnelTransportInfo tunnelTransportInfo) { ITransport _transports = transports.FirstOrDefault(c => c.Name == tunnelTransportInfo.TransportName && c.Type == tunnelTransportInfo.TransportType); if (_transports != null) { _transports.OnBegin(tunnelTransportInfo); } } public void OnFail(TunnelTransportInfo tunnelTransportInfo) { ITransport _transports = transports.FirstOrDefault(c => c.Name == tunnelTransportInfo.TransportName && c.Type == tunnelTransportInfo.TransportType); if (_transports != null) { _transports.OnFail(tunnelTransportInfo); OnConnectFail(tunnelTransportInfo.FromMachineName); } } public async Task Info(TunnelTransportExternalIPRequestInfo request) { return await GetLocalInfo(request.TransportType); } private async Task GetLocalInfo(ProtocolType transportType) { TunnelCompactIPEndPoint[] ips = await compactTransfer.GetExternalIPAsync(transportType); if (ips != null && ips.Length > 0) { return new TunnelTransportExternalIPInfo { Local = ips[0].Local, Remote = ips[0].Remote, RouteLevel = config.Data.Client.Tunnel.RouteLevel, MachineName = config.Data.Client.Name }; } return null; } private async Task GetRemoteInfo(string remoteMachineName, ProtocolType transportType) { MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap { Connection = clientSignInState.Connection, MessengerId = (ushort)TunnelMessengerIds.InfoForward, Timeout = 3000, Payload = MemoryPackSerializer.Serialize(new TunnelTransportExternalIPRequestInfo { RemoteMachineName = remoteMachineName, TransportType = transportType, }) }); if (resp.Code == MessageResponeCodes.OK && resp.Data.Length > 0) { return MemoryPackSerializer.Deserialize(resp.Data.Span); } return null; } private async Task OnSendConnectBegin(TunnelTransportInfo tunnelTransportInfo) { await messengerSender.SendReply(new MessageRequestWrap { Connection = clientSignInState.Connection, MessengerId = (ushort)TunnelMessengerIds.BeginForward, Payload = MemoryPackSerializer.Serialize(tunnelTransportInfo) }); } private async Task OnSendConnectFail(TunnelTransportInfo tunnelTransportInfo) { await messengerSender.SendReply(new MessageRequestWrap { Connection = clientSignInState.Connection, MessengerId = (ushort)TunnelMessengerIds.FailForward, Payload = MemoryPackSerializer.Serialize(tunnelTransportInfo) }); } public Dictionary Connections { get; } = new Dictionary(); private int connectionsChangeFlag = 1; public bool ConnectionChanged => Interlocked.CompareExchange(ref connectionsChangeFlag, 0, 1) == 1; private void OnConnecting(TunnelTransportInfo tunnelTransportInfo) { if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) { Logger.Instance.Debug($"tunnel connect [{tunnelTransportInfo.TransactionId}]->{tunnelTransportInfo.Remote.MachineName}"); } CheckDic(tunnelTransportInfo.Remote.MachineName, out TunnelConnectInfo info); info.Status = TunnelConnectStatus.Connecting; Interlocked.Exchange(ref connectionsChangeFlag, 1); } private void OnConnectBegin(TunnelTransportInfo tunnelTransportInfo) { if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) { Logger.Instance.Debug($"tunnel connect from {tunnelTransportInfo.Local.MachineName}"); } CheckDic(tunnelTransportInfo.Local.MachineName, out TunnelConnectInfo info); info.Status = TunnelConnectStatus.Connecting; Interlocked.Exchange(ref connectionsChangeFlag, 1); } private void _OnConnected(TunnelTransportState state) { if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) { Logger.Instance.Debug($"tunnel connect [{state.TransactionId}]->{state.RemoteMachineName} success"); } CheckDic(state.RemoteMachineName, out TunnelConnectInfo info); info.Status = TunnelConnectStatus.Connected; info.State = state; Interlocked.Exchange(ref connectionsChangeFlag, 1); } private void OnDisConnected(TunnelTransportState state) { CheckDic(state.RemoteMachineName, out TunnelConnectInfo info); info.Status = TunnelConnectStatus.None; info.State = null; Interlocked.Exchange(ref connectionsChangeFlag, 1); } private void OnConnectFail(string machineName) { if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) { Logger.Instance.Error($"tunnel connect {machineName} fail"); } CheckDic(machineName, out TunnelConnectInfo info); info.Status = TunnelConnectStatus.None; info.State = null; Interlocked.Exchange(ref connectionsChangeFlag, 1); } private void CheckDic(string name, out TunnelConnectInfo info) { if (Connections.TryGetValue(name, out info) == false) { info = new TunnelConnectInfo(); Connections[name] = info; } } public sealed class TunnelConnectInfo { public TunnelConnectStatus Status { get; set; } public TunnelTransportState State { get; set; } } public enum TunnelConnectStatus { None = 0, Connecting = 1, Connected = 2, } } }