/// <summary> /// Used to initialize the file transfer sequence. /// <para>Sends a file request with the file id, name, and size.</para> /// </summary> /// /// <param name="FilePath">The full path to the file to send</param> public void SendFile(string FilePath) { // store file length long len = new FileInfo(FilePath).Length; // increment file id _fileCounter++; // get an open port int port = _clientSocket.NextOpenPort(); // create the file info header byte[] btInfo = new DtmFileInfo(Path.GetFileName(FilePath), len, port).ToBytes(); // create a new symmetric key KeyParams fileKey = GenerateSymmetricKey(_srvIdentity.Session); MemoryStream keyStrm = (MemoryStream)KeyParams.Serialize(fileKey); // add the key btInfo = ArrayUtils.Concat(btInfo, keyStrm.ToArray()); // wrap the request btInfo = WrapMessage(btInfo, _dtmParameters.MaxMessageAppend, _dtmParameters.MaxMessagePrePend); // encrypt with master btInfo = SymmetricTransform(_srvSymProcessor, btInfo); // initialize the files unique crypto processor ICipherMode fileSymProcessor = SymmetricInit(_srvIdentity.Session, fileKey); // tune for parallel processing int blockSize = ((int)MessageBufferSize - DtmPacket.GetHeaderSize()) - ((int)MessageBufferSize - DtmPacket.GetHeaderSize()) % ((CTR)fileSymProcessor).ParallelMinimumSize; ((CTR)fileSymProcessor).ParallelBlockSize = blockSize; // build the file transfer instance DtmFileTransfer fileTransfer = new DtmFileTransfer(fileSymProcessor, _fileCounter, 1024, (int)FileBufferSize); fileTransfer.FileTransferred += new DtmFileTransfer.FileTransferredDelegate(OnFileSent); fileTransfer.ProgressPercent += new DtmFileTransfer.ProgressDelegate(OnFileSentProgress); // add to dictionary _transQueue.TryAdd(_fileCounter, fileTransfer); // send header to the remote host in a file request Transmit(DtmPacketTypes.Transfer, (short)DtmTransferFlags.Request, _fileCounter, new MemoryStream(btInfo)); // initiate with non-blocking listen fileTransfer.StartSend(_clientSocket.LocalAddress, port, FilePath); if (fileTransfer.IsConnected) { try { // start on a new thread Task socketTask = Task.Factory.StartNew(() => { fileTransfer.SendFile(); }); socketTask.Wait(10); } catch (AggregateException ae) { if (SessionError != null) SessionError(this, new DtmErrorEventArgs(ae.GetBaseException(), DtmErrorSeverity.Warning)); } } else { // remove from pending and dispose CloseTransfer(_fileCounter); // alert app DtmErrorEventArgs args = new DtmErrorEventArgs(new SocketException((int)SocketError.ConnectionAborted), DtmErrorSeverity.Connection); if (SessionError != null) SessionError(this, args); if (args.Cancel == true) Disconnect(); } }
/// <summary> /// Sends a packet with increasing wait times. /// <para>After 4 attempts fires a SessionError with optional cancellation token.</para> /// </summary> /// /// <param name="PacketStream">The packet to send</param> private void Throttle(MemoryStream PacketStream) { int maxwait = 10; for (int i = 0; i < 4; i++) { try { Wait(maxwait); _clientSocket.SendAsync(PacketStream); break; } catch (CryptoSocketException ce) { SocketException se = ce.InnerException as SocketException; if (se.SocketErrorCode == SocketError.WouldBlock || se.SocketErrorCode == SocketError.IOPending || se.SocketErrorCode == SocketError.NoBufferSpaceAvailable) { // buffer is full maxwait *= 2; } else { // possible connection dropped, alert app if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(ce, DtmErrorSeverity.Warning); SessionError(this, args); if (args.Cancel == true) Disconnect(); } } } } // all attempts have failed if (maxwait > 160) { // possible connection dropped, alert app if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(new SocketException((int)SocketError.HostUnreachable), DtmErrorSeverity.DataLoss); SessionError(this, args); if (args.Cancel == true) Disconnect(); } } }
/// <summary> /// Frame and Transmit the packet to the remote client /// </summary> /// /// <param name="PacketType">The packet class</param> /// <param name="PacketFlag">The packet message type flag</param> /// <param name="OptionFlag">The option flag</param> /// <param name="Payload">The packet payload flag</param> /// <param name="Blocking">Blocking or Async transmit</param> private void Transmit(DtmPacketTypes PacketType, short PacketFlag, long OptionFlag = 0, MemoryStream Payload = null, bool Blocking = false) { lock (_sendLock) { long pldLen = Payload == null ? 0 : Payload.Length; // create a new packet: packet flag, payload size, sequence, and state flag MemoryStream pktStm = new DtmPacket(PacketType, pldLen, _sndSequence, PacketFlag, OptionFlag).ToStream(); // add payload if (Payload != null) { // store total encrypted bytes sent if (_isEstablished) _bytesSent += Payload.Length; // copy to output pktStm.Seek(0, SeekOrigin.End); Payload.WriteTo(pktStm); pktStm.Seek(0, SeekOrigin.Begin); } // service requests are not buffered if (PacketType != DtmPacketTypes.Service) { // store in the packet buffer _sndBuffer.Push(_sndSequence, pktStm); } // increment send counter _sndSequence++; // transmit to remote client if (_clientSocket.IsConnected) { if (Blocking) { try { _clientSocket.SendAsync(pktStm); } catch (CryptoSocketException ce) { SocketException se = ce.InnerException as SocketException; if (se.SocketErrorCode == SocketError.WouldBlock || se.SocketErrorCode == SocketError.IOPending || se.SocketErrorCode == SocketError.NoBufferSpaceAvailable) { // buffer is full, slow down Throttle(pktStm); } else if (se.SocketErrorCode != SocketError.Success) { // possible connection dropped, alert app if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(ce, DtmErrorSeverity.Connection); SessionError(this, args); if (args.Cancel == true) Disconnect(); } } } } else { try { pktStm.WriteTo(_clientSocket.TcpStream); } catch (Exception ex) { // internal error, alert app if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(ex, DtmErrorSeverity.Critical); SessionError(this, args); if (args.Cancel == true) Disconnect(); } } } // notify app if (PacketSent != null) PacketSent(this, new DtmPacketEventArgs((short)_exchangeState, pldLen)); } else { // possible connection dropped, alert app if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(new SocketException((int)SocketError.ConnectionReset), DtmErrorSeverity.Connection); SessionError(this, args); if (args.Cancel == true) Disconnect(); } } } }
/// <summary> /// Used to process a resync response. /// <para>The remote host has sent the number of bytes encrypted as the OptionFlag in the DtmPacket. /// The resynchronization of the crypto stream involves first encrypting an equal sized array, /// and then testing for validity by decrypting the payload and comparing it to the stored client id. /// If the Resync fails, the client Disconnects, notifies the application, and performs a teardown of the VPN.</para> /// </summary> /// /// <param name="PacketStream">A resync packet</param> private void ProcessResync(MemoryStream PacketStream) { // get the header DtmPacket pktHdr = new DtmPacket(PacketStream); int len = (int)(pktHdr.OptionFlag - pktHdr.PayloadLength - _bytesReceived); if (len > 0) { byte[] pad = new byte[len]; // sync the cipher stream SymmetricTransform(_cltSymProcessor, pad); } else if (len < 0) { // can't resync, alert user and disconnect DtmErrorEventArgs args = new DtmErrorEventArgs(new InvalidDataException("The data stream could not be resynced, connection aborted!"), DtmErrorSeverity.Critical); if (SessionError != null) SessionError(this, args); Disconnect(); return; } // read the packet byte[] data = new byte[pktHdr.PayloadLength]; // get the encrypted data PacketStream.Read(data, 0, data.Length); // decrypt the payload byte[] id = SymmetricTransform(_cltSymProcessor, data); // remove random padding id = UnwrapMessage(id); // compare to stored id if (!ArrayUtils.AreEqual(id, _cltIdentity.Identity)) { // resync failed, abort connection DtmErrorEventArgs args = new DtmErrorEventArgs(new InvalidDataException("The data stream could not be resynced, connection aborted!"), DtmErrorSeverity.Critical); if (SessionError != null) SessionError(this, args); Disconnect(); return; } }
/// <summary> /// Resend a packet to a host /// </summary> private void Resend(DtmPacket Packet) { if (_sndBuffer.Exists(Packet.Sequence)) { _maxSendCounter++; // limit attack scope with session resend max if (_maxSendCounter > MaxResend) { // let the app decide what to do next DtmErrorEventArgs args = new DtmErrorEventArgs(new InvalidDataException("The stream has encountered data loss, attempting to resync.."), DtmErrorSeverity.DataLoss); if (SessionError != null) SessionError(this, args); if (args.Cancel) { Disconnect(); return; } } try { MemoryStream pktStm = _sndBuffer.Peek(Packet.Sequence); if (pktStm != null) { if (pktStm.Length > 0) pktStm.WriteTo(_clientSocket.TcpStream); _sndSequence++; } } catch { // packet lost, request a resync Transmit(DtmPacketTypes.Service, (short)DtmServiceFlags.DataLost); } } else { // packet lost, request a resync Transmit(DtmPacketTypes.Service, (short)DtmServiceFlags.DataLost); } }
/// <summary> /// Process a message. /// <para>Use this method to process <see cref="DtmPacket"/> data sent to the server</para> /// </summary> private void Process(MemoryStream PacketStream) { try { // increment rcv sequence _rcvSequence++; // get the header DtmPacket pktHdr = new DtmPacket(PacketStream); PacketStream.Seek(0, SeekOrigin.Begin); switch (pktHdr.PacketType) { // message stream case DtmPacketTypes.Message: { // process message switch ((DtmMessageFlags)pktHdr.PacketFlag) { case DtmMessageFlags.Transmission: { try { // received stream data ReceiveMessage(PacketStream); } catch (Exception) { // packet corrupted, request a retransmission and exit Transmit(DtmPacketTypes.Service, (short)DtmServiceFlags.Resend, pktHdr.Sequence); return; } // echo the packet to remove it from remote buffer Transmit(DtmPacketTypes.Service, (short)DtmServiceFlags.Echo, pktHdr.Sequence); break; } } break; } // service messages case DtmPacketTypes.Service: { switch ((DtmServiceFlags)pktHdr.PacketFlag) { case DtmServiceFlags.KeepAlive: { // reset the keep alive counter _pulseCounter = 0; break; } // process echo case DtmServiceFlags.Echo: { // remove from buffer if (_sndBuffer.Exists(pktHdr.OptionFlag)) _sndBuffer.Destroy(pktHdr.OptionFlag); break; } case DtmServiceFlags.Resend: { // attempt resend, if not in buffer transmission, attempts a resync Resend(pktHdr); break; } case DtmServiceFlags.DataLost: { // remote packet lost, try resync. note: if this happens often, increase buffer size in ctor + tcp MemoryStream pktData = CreateResync(); _bytesSent += pktData.Length; Transmit(DtmPacketTypes.Service, (short)DtmServiceFlags.Resync, _bytesSent, pktData); break; } case DtmServiceFlags.Resync: { // attempt to resync the crypto stream ProcessResync(PacketStream); break; } case DtmServiceFlags.Refusal: { DtmErrorEventArgs args = new DtmErrorEventArgs(new ApplicationException("The session was refused by the remote host."), DtmErrorSeverity.Connection); if (SessionError != null) SessionError(this, args); if (args.Cancel) Disconnect(); break; } case DtmServiceFlags.Terminate: { // reserved DtmErrorEventArgs args = new DtmErrorEventArgs(new ApplicationException("The session was terminated by the remote host."), DtmErrorSeverity.Critical); if (SessionError != null) SessionError(this, args); Disconnect(); break; } } break; } // file transfer case DtmPacketTypes.Transfer: { switch ((DtmTransferFlags)pktHdr.PacketFlag) { case DtmTransferFlags.Request: { // received file transfer request ReceiveFile(PacketStream); break; } case DtmTransferFlags.Refused: { // refused by remote DtmErrorEventArgs args = new DtmErrorEventArgs(new ApplicationException("The session was refused by the remote host."), DtmErrorSeverity.Connection); if (SessionError != null) SessionError(this, args); CloseTransfer(pktHdr.OptionFlag); break; } case DtmTransferFlags.Received: { // refused by remote CloseTransfer(pktHdr.OptionFlag); break; } } break; } // key exchange case DtmPacketTypes.Exchange: { // process message switch ((DtmExchangeFlags)pktHdr.PacketFlag) { case DtmExchangeFlags.Connect: { // received public id ProcessConnect(PacketStream); break; } case DtmExchangeFlags.Init: { // received auth-stage params ProcessInit(PacketStream); break; } case DtmExchangeFlags.PreAuth: { // received public key ProcessPreAuth(PacketStream); break; } case DtmExchangeFlags.AuthEx: { // received symmetric key ProcessAuthEx(PacketStream); break; } case DtmExchangeFlags.Auth: { // received private id ProcessAuth(PacketStream); break; } case DtmExchangeFlags.Sync: { // received primary public key params ProcessSync(PacketStream); break; } case DtmExchangeFlags.Primary: { // received primary public key ProcessPrimary(PacketStream); break; } case DtmExchangeFlags.PrimeEx: { // received primary session key ProcessPrimeEx(PacketStream); break; } case DtmExchangeFlags.Established: { // received ack established ProcessEstablish(PacketStream); break; } } break; } default: { if (SessionError != null) SessionError(this, new DtmErrorEventArgs(new CryptoProcessingException("DtmKex:Process", "The data transmission encountered an error!", new InvalidDataException()), DtmErrorSeverity.Critical)); break; } } // notify app if (PacketReceived != null) PacketReceived(this, new DtmPacketEventArgs(pktHdr.PacketFlag, pktHdr.PayloadLength)); } catch (Exception ex) { if (SessionError != null) SessionError(this, new DtmErrorEventArgs(new CryptoProcessingException("DtmKex:Process", "The data transmission encountered an error!", ex), DtmErrorSeverity.Critical)); } }
/// <summary> /// The keep alive timer event handler /// </summary> private void OnTimerPulse(object sender, ElapsedEventArgs e) { _pulseCounter++; // default trigger is 30 seconds without a keep alive if (_pulseCounter > ConnectionTimeOut) { if (_autoReconnect) { // attempt to reconnect if (!Reconnect()) { // connection unvailable if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(new SocketException((int)SocketError.ConnectionReset), DtmErrorSeverity.Critical); SessionError(this, args); Disconnect(); } } else { // resync the crypto stream Transmit(DtmPacketTypes.Service, (short)DtmServiceFlags.DataLost); } } else { // possible connection dropped, alert app if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(new SocketException((int)SocketError.ConnectionReset), DtmErrorSeverity.Critical); SessionError(this, args); if (args.Cancel == true) Disconnect(); } } } else { Transmit(DtmPacketTypes.Service, (short)DtmServiceFlags.KeepAlive); } }
/// <summary> /// Used to read a blocking message response /// </summary> private MemoryStream BlockingReceive() { MemoryStream pktStm = null; try { // get the header pktStm = _clientSocket.GetStreamData(DtmPacket.GetHeaderSize(), EXCHTIMEOUT); DtmPacket pktHdr = new DtmPacket(pktStm); // add the payload if (pktHdr.PayloadLength > 0) _clientSocket.GetStreamData((int)pktHdr.PayloadLength, EXCHTIMEOUT).WriteTo(pktStm); pktStm.Seek(0, SeekOrigin.Begin); } catch (ObjectDisposedException) { // host is disconnected, notify app DtmErrorEventArgs args = new DtmErrorEventArgs(new SocketException((int)SocketError.HostDown), DtmErrorSeverity.Connection); if (SessionError != null) SessionError(this, args); if (args.Cancel == true) Disconnect(); } if (pktStm == null || pktStm.Length == 0) { // exchange failed if (SessionError != null) SessionError(this, new DtmErrorEventArgs(new SocketException((int)SocketError.HostUnreachable), DtmErrorSeverity.Critical)); Disconnect(); } return pktStm; }
private void Transmit(DtmPacketTypes PacketType, short PacketFlag, long OptionFlag = 0, MemoryStream Payload = null) { lock (_sndLock) { long pldLen = Payload == null ? 0 : Payload.Length; // create a new packet: packet flag, payload size, sequence, and state flag MemoryStream pktStm = new DtmPacket(PacketType, pldLen, _sndSequence, PacketFlag, OptionFlag).ToStream(); // add payload if (Payload != null) { // copy to output pktStm.Seek(0, SeekOrigin.End); Payload.WriteTo(pktStm); pktStm.Seek(0, SeekOrigin.Begin); } // store in the file packet buffer _sndBuffer.Push(_sndSequence, pktStm); // increment file send counter _sndSequence++; // transmit to remote client if (_clientSocket.IsConnected) { try { _clientSocket.SendAsync(pktStm); } catch (CryptoSocketException ce) { SocketException se = ce.InnerException as SocketException; if (se.SocketErrorCode == SocketError.WouldBlock || se.SocketErrorCode == SocketError.IOPending || se.SocketErrorCode == SocketError.NoBufferSpaceAvailable) { // buffer is full, throttle down Throttle(pktStm); } else { // possible connection dropped, alert app if (SessionError != null) { DtmErrorEventArgs args = new DtmErrorEventArgs(ce, DtmErrorSeverity.Connection); SessionError(this, args); } } } catch (Exception) { } // notify app if (PacketSent != null) PacketSent(this, new DtmPacketEventArgs((short)DtmTransferFlags.DataChunk, pldLen)); } } }
/// <summary> /// Fires when an error has occured; contains the exception and the errors operational severity /// </summary> private void OnSessionError(object owner, DtmErrorEventArgs args) { // in case window is closed; should call disconnect in a forms closing event if (_dtmServer.IsConnected) Console.WriteLine(CON_TITLE + "Severity:" + (DtmErrorSeverity)args.Severity + "Message: " + args.Message); }