/// <summary> /// Send data to client. /// </summary> /// <param name="data">Data array to send.</param> /// <exception cref="Exception">Thrown if destination is null or destinationPort is 0.</exception> /// <exception cref="ArgumentException">Thrown on fatal error (contact support).</exception> /// <exception cref="OverflowException">Thrown if data array length is greater than Int32.MaxValue.</exception> /// <exception cref="Sys.IO.IOException">Thrown on IO error.</exception> /// <exception cref="Exception">Thrown if TCP Status is not ESTABLISHED.</exception> public void Send(byte[] data) { if ((StateMachine.destination == null) || (StateMachine.destinationPort == 0)) { throw new InvalidOperationException("Must establish a default remote host by calling Connect() before using this Send() overload"); } if (StateMachine.Status != Status.ESTABLISHED) { throw new Exception("Client must be connected before sending data."); } if (data.Length > 536) { var chunks = ArrayHelper.ArraySplit(data, 536); for (int i = 0; i < chunks.Length; i++) { var packet = new TCPPacket(StateMachine.source, StateMachine.destination, (ushort)StateMachine.localPort, (ushort)StateMachine.destinationPort, StateMachine.SequenceNumber, StateMachine.AckNumber, 20, i == chunks.Length - 2 ? (byte)(Flags.PSH | Flags.ACK) : (byte)(Flags.ACK), 0xFAF0, 0, chunks[i]); OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); StateMachine.SequenceNumber += (uint)chunks[i].Length; } } else { var packet = new TCPPacket(StateMachine.source, StateMachine.destination, (ushort)StateMachine.localPort, (ushort)StateMachine.destinationPort, StateMachine.SequenceNumber, StateMachine.AckNumber, 20, (byte)(Flags.PSH | Flags.ACK), 0xFAF0, 0, data); OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); StateMachine.SequenceNumber += (uint)data.Length; } StateMachine.WaitingAck = true; }
/// <summary> /// Connect to client. /// </summary> /// <param name="dest">Destination address.</param> /// <param name="destPort">Destination port.</param> /// <exception cref="Exception">Thrown if TCP Status is not CLOSED.</exception> public void Connect(Address dest, int destPort, int timeout = 5000) { if (StateMachine.Status != Status.CLOSED) { throw new Exception("Client must be closed before setting a new connection."); } StateMachine.destination = dest; StateMachine.destinationPort = destPort; StateMachine.source = IPConfig.FindNetwork(dest); //Generate Random Sequence Number var rnd = new Random(); StateMachine.SequenceNumber = (uint)((rnd.Next(0, Int32.MaxValue)) << 32) | (uint)(rnd.Next(0, Int32.MaxValue)); // Flags=0x02 -> Syn var packet = new TCPPacket(StateMachine.source, StateMachine.destination, (ushort)StateMachine.localPort, (ushort)destPort, StateMachine.SequenceNumber, 0, 20, (byte)Flags.SYN, 0xFAF0, 0); OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); StateMachine.Status = Status.SYN_SENT; if (StateMachine.WaitStatus(Status.ESTABLISHED, timeout) == false) { throw new Exception("Failed to open TCP connection!"); } }
/// <summary> /// TCP handler. /// </summary> /// <param name="packetData">Packet data.</param> /// <exception cref="sys.ArgumentOutOfRangeException">Thrown on fatal error (contact support).</exception> /// <exception cref="sys.IO.IOException">Thrown on IO error.</exception> /// <exception cref="sys.ArgumentException">Thrown on fatal error (contact support).</exception> /// <exception cref="sys.OverflowException">Thrown if packetData array length is greater than Int32.MaxValue.</exception> internal static void TCPHandler(byte[] packetData) { var packet = new TCPPacket(packetData); if (packet.CheckCRC()) { var client = TcpClient.GetClient(packet.DestinationPort); if (client != null) { client.StateMachine.ReceiveData(packet); return; } var listener = TcpListener.GetListener(packet.DestinationPort); if (listener != null) { listener.StateMachine.ReceiveData(packet); return; } } else { Global.mDebugger.Send("Checksum incorrect! Packet passed."); } }
/// <summary> /// Send acknowledgement packet /// </summary> private void SendEmptyPacket(Flags flag) { var packet = new TCPPacket(source, destination, (ushort)localPort, (ushort)destinationPort, SequenceNumber, AckNumber, 20, (byte)flag, 0xFAF0, 0); OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); }
/// <summary> /// Process FIN_WAIT1 Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessFinWait1(TCPPacket packet) { if (packet.ACK) { if (packet.FIN) { TCB.RcvNxt++; SendEmptyPacket(Flags.ACK); WaitAndClose(); } else { Status = Status.FIN_WAIT2; } } else if (packet.FIN) { TCB.RcvNxt++; SendEmptyPacket(Flags.ACK); Status = Status.CLOSING; } }
/// <summary> /// Process CLOSING Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessClosing(TCPPacket packet) { if (packet.ACK) { WaitAndClose(); } }
/// <summary> /// Process Close_WAIT Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessCloseWait(TCPPacket packet) { if (packet.ACK) { Status = Status.CLOSED; } }
/// <summary> /// Close connection. /// </summary> /// <exception cref="ArgumentOutOfRangeException">Thrown on fatal error (contact support).</exception> /// <exception cref="Exception">Thrown if TCP Status is CLOSED.</exception> public void Close() { if (StateMachine.Status == Status.CLOSED) { throw new Exception("Client already closed."); } if (StateMachine.Status == Status.ESTABLISHED) { var packet = new TCPPacket(StateMachine.source, StateMachine.destination, (ushort)StateMachine.localPort, (ushort)StateMachine.destinationPort, StateMachine.SequenceNumber, StateMachine.AckNumber, 20, (byte)(Flags.FIN | Flags.ACK), 0xFAF0, 0); OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); StateMachine.SequenceNumber++; StateMachine.Status = Status.FIN_WAIT1; if (StateMachine.WaitStatus(Status.CLOSED, 5000) == false) { throw new Exception("Failed to close TCP connection!"); } } if (clients.ContainsKey((uint)StateMachine.localPort)) { clients.Remove((uint)StateMachine.localPort); } }
/// <summary> /// Send data to client. /// </summary> /// <param name="data">Data array to send.</param> /// <exception cref="Exception">Thrown if destination is null or destinationPort is 0.</exception> /// <exception cref="ArgumentException">Thrown on fatal error (contact support).</exception> /// <exception cref="OverflowException">Thrown if data array length is greater than Int32.MaxValue.</exception> /// <exception cref="Sys.IO.IOException">Thrown on IO error.</exception> /// <exception cref="Exception">Thrown if TCP Status is not ESTABLISHED.</exception> public void Send(byte[] data) { if ((StateMachine.RemoteEndPoint.Address == null) || (StateMachine.RemoteEndPoint.Port == 0)) { throw new InvalidOperationException("Must establish a default remote host by calling Connect() before using this Send() overload"); } if (StateMachine.Status != Status.ESTABLISHED) { throw new Exception("Client must be connected before sending data."); } if (data.Length > 536) { var chunks = ArrayHelper.ArraySplit(data, 536); for (int i = 0; i < chunks.Length; i++) { var packet = new TCPPacket(StateMachine.LocalEndPoint.Address, StateMachine.RemoteEndPoint.Address, StateMachine.LocalEndPoint.Port, StateMachine.RemoteEndPoint.Port, StateMachine.TCB.SndNxt, StateMachine.TCB.RcvNxt, 20, i == chunks.Length - 2 ? (byte)(Flags.PSH | Flags.ACK) : (byte)(Flags.ACK), StateMachine.TCB.SndWnd, 0, chunks[i]); OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); StateMachine.TCB.SndNxt += (uint)chunks[i].Length; } } else { var packet = new TCPPacket(StateMachine.LocalEndPoint.Address, StateMachine.RemoteEndPoint.Address, StateMachine.LocalEndPoint.Port, StateMachine.RemoteEndPoint.Port, StateMachine.TCB.SndNxt, StateMachine.TCB.RcvNxt, 20, (byte)(Flags.PSH | Flags.ACK), StateMachine.TCB.SndWnd, 0, data); OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); StateMachine.TCB.SndNxt += (uint)data.Length; } }
/// <summary> /// Send TCP packet. /// </summary> private void SendPacket(TCPPacket packet) { OutgoingBuffer.AddPacket(packet); NetworkStack.Update(); if (packet.SYN || packet.FIN) { TCB.SndNxt++; } }
/// <summary> /// Process FIN_WAIT2 Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessFinWait2(TCPPacket packet) { if (packet.FIN) { TCB.RcvNxt++; SendEmptyPacket(Flags.ACK); WaitAndClose(); } }
/// <summary> /// Process FIN_WAIT2 Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessFinWait2(TCPPacket packet) { if (packet.FIN) { AckNumber++; SendEmptyPacket(Flags.ACK); WaitAndClose(); } }
/// <summary> /// Handle incoming TCP packets according to current connection status. /// </summary> /// <param name="packet">Packet to receive.</param> /// <exception cref="OverflowException">Thrown on fatal error (contact support).</exception> /// <exception cref="Sys.IO.IOException">Thrown on IO error.</exception> internal void ReceiveData(TCPPacket packet) { Global.mDebugger.Send("[" + table[(int)Status] + "] " + packet.ToString()); switch (Status) { case Status.LISTEN: ProcessListen(packet); break; case Status.SYN_SENT: ProcessSynSent(packet); break; case Status.SYN_RECEIVED: ProcessSynReceived(packet); break; case Status.ESTABLISHED: ProcessEstablished(packet); break; case Status.FIN_WAIT1: ProcessFinWait1(packet); break; case Status.FIN_WAIT2: ProcessFinWait2(packet); break; case Status.CLOSE_WAIT: ProcessCloseWait(packet); break; case Status.CLOSING: ProcessClosing(packet); break; case Status.LAST_ACK: ProcessCloseWait(packet); break; case Status.TIME_WAIT: break; case Status.CLOSED: break; default: throw new Exception("Unknown TCP connection state."); } }
/// <summary> /// Process LISTEN Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessListen(TCPPacket packet) { if (packet.RST) { Global.mDebugger.Send("RST received at LISTEN state, packet passed."); return; } else if (packet.FIN) { Status = Status.CLOSED; Global.mDebugger.Send("TCP connection closed! (FIN received on LISTEN state)"); } else if (packet.ACK) { TCB.RcvNxt = packet.SequenceNumber; TCB.SndNxt = packet.AckNumber; Status = Status.ESTABLISHED; } else if (packet.SYN) { LocalEndPoint.Address = IPConfig.FindNetwork(packet.SourceIP); RemoteEndPoint.Address = packet.SourceIP; RemoteEndPoint.Port = packet.SourcePort; var rnd = new Random(); var sequenceNumber = (uint)((rnd.Next(0, Int32.MaxValue)) << 32) | (uint)(rnd.Next(0, Int32.MaxValue)); //Fill TCB TCB.SndUna = sequenceNumber; TCB.SndNxt = sequenceNumber; TCB.SndWnd = Tcp.TcpWindowSize; TCB.SndUp = 0; TCB.SndWl1 = packet.SequenceNumber - 1; TCB.SndWl2 = 0; TCB.ISS = sequenceNumber; TCB.RcvNxt = packet.SequenceNumber + 1; TCB.RcvWnd = Tcp.TcpWindowSize; TCB.RcvUp = 0; TCB.IRS = packet.SequenceNumber; SendEmptyPacket(Flags.SYN | Flags.ACK); Status = Status.SYN_RECEIVED; } }
/// <summary> /// Process SYN_RECEIVED Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessSynReceived(TCPPacket packet) { if (packet.RST) { Status = Status.LISTEN; } else if (packet.ACK) { LastSequenceNumber = packet.SequenceNumber - 1; //TODO: Fix this trick (for dup check when PSH ACK) SequenceNumber++; Status = Status.ESTABLISHED; } }
/// <summary> /// Process SYN_SENT Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessSynSent(TCPPacket packet) { if (packet.FIN) { Status = Status.CLOSED; throw new Exception("TCP connection closed! (FIN received on SYN_SENT state)"); } else if (packet.TCPFlags == (byte)Flags.ACK) { AckNumber = packet.SequenceNumber; SequenceNumber = packet.AckNumber; Status = Status.ESTABLISHED; } else if (packet.RST) { Status = Status.CLOSED; throw new Exception("Connection refused by remote computer."); } else if (packet.SYN) { if (packet.ACK) { AckNumber = packet.SequenceNumber + 1; SequenceNumber++; LastSequenceNumber = packet.SequenceNumber; SendEmptyPacket(Flags.ACK); Status = Status.ESTABLISHED; } else if (packet.TCPFlags == (byte)Flags.SYN) { Status = Status.CLOSED; throw new NotImplementedException("Simultaneous open not supported."); } else { Status = Status.CLOSED; throw new Exception("TCP connection closed! (Flag " + packet.TCPFlags + " received on SYN_SENT state)"); } } }
/// <summary> /// Process SYN_RECEIVED Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessSynReceived(TCPPacket packet) { if (packet.ACK) { if (TCB.SndUna <= packet.AckNumber && packet.AckNumber <= TCB.SndNxt) { TCB.SndWnd = packet.WindowSize; TCB.SndWl1 = packet.SequenceNumber; TCB.SndWl2 = packet.SequenceNumber; Status = Status.ESTABLISHED; } else { SendEmptyPacket(Flags.RST, packet.AckNumber); } } }
/// <summary> /// TCP handler. /// </summary> /// <param name="packetData">Packet data.</param> /// <exception cref="sys.ArgumentOutOfRangeException">Thrown on fatal error (contact support).</exception> /// <exception cref="sys.IO.IOException">Thrown on IO error.</exception> /// <exception cref="sys.ArgumentException">Thrown on fatal error (contact support).</exception> /// <exception cref="sys.OverflowException">Thrown if packetData array length is greater than Int32.MaxValue.</exception> internal static void TCPHandler(byte[] packetData) { var packet = new TCPPacket(packetData); if (packet.CheckCRC()) { var connection = Tcp.GetConnection(packet.DestinationPort, packet.SourcePort, packet.DestinationIP, packet.SourceIP); if (connection != null) { connection.ReceiveData(packet); } } else { Global.mDebugger.Send("Checksum incorrect! Packet passed."); } }
/// <summary> /// Process LISTEN Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessListen(TCPPacket packet) { if (packet.RST) { Status = Status.CLOSED; throw new Exception("TCP connection resetted! (RST received on LISTEN state)"); } else if (packet.FIN) { Status = Status.CLOSED; throw new Exception("TCP connection closed! (FIN received on LISTEN state)"); } else if (packet.ACK) { AckNumber = packet.SequenceNumber; SequenceNumber = packet.AckNumber; Status = Status.ESTABLISHED; } else if (packet.SYN) { Status = Status.SYN_RECEIVED; source = IPConfig.FindNetwork(packet.SourceIP); AckNumber = packet.SequenceNumber + 1; var rnd = new Random(); SequenceNumber = (uint)((rnd.Next(0, Int32.MaxValue)) << 32) | (uint)(rnd.Next(0, Int32.MaxValue)); destination = packet.SourceIP; destinationPort = packet.SourcePort; SendEmptyPacket(Flags.SYN | Flags.ACK); } }
/// <summary> /// Process ESTABLISHED Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessEstablished(TCPPacket packet) { if (packet.ACK && !packet.FIN) { if (packet.PSH) { if (packet.SequenceNumber > LastSequenceNumber) //dup check { AckNumber += packet.TCP_DataLength; LastSequenceNumber = packet.SequenceNumber; Data = ArrayHelper.Concat(Data, packet.TCP_Data); rxBuffer.Enqueue(packet); SendEmptyPacket(Flags.ACK); } } if (WaitingAck) { if (packet.AckNumber == SequenceNumber) { WaitingAck = false; } } else if (!packet.PSH) { if (packet.SequenceNumber >= AckNumber && packet.TCP_DataLength > 0) //packet sequencing { AckNumber += packet.TCP_DataLength; Data = ArrayHelper.Concat(Data, packet.TCP_Data); } } return; } if (packet.RST) { Status = Status.CLOSED; throw new Exception("TCP Connection resetted!"); } else if (packet.FIN && packet.ACK) { AckNumber++; SendEmptyPacket(Flags.ACK); WaitAndClose(); } else if (packet.FIN) { AckNumber++; SendEmptyPacket(Flags.ACK); Status = Status.CLOSE_WAIT; HAL.Global.PIT.Wait(300); SendEmptyPacket(Flags.FIN); Status = Status.LAST_ACK; } }
/// <summary> /// Process ESTABLISHED Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessEstablished(TCPPacket packet) { if (packet.ACK) { if (TCB.SndUna < packet.AckNumber && packet.AckNumber <= TCB.SndNxt) { TCB.SndUna = packet.AckNumber; //Update Window Size if (TCB.SndWl1 < packet.SequenceNumber || (TCB.SndWl1 == packet.SequenceNumber && TCB.SndWl2 <= packet.AckNumber)) { TCB.SndWnd = packet.WindowSize; TCB.SndWl1 = packet.SequenceNumber; TCB.SndWl2 = packet.AckNumber; } } // Check for duplicate packet if (packet.AckNumber < TCB.SndUna) { return; } // Something not yet sent if (packet.AckNumber > TCB.SndNxt) { SendEmptyPacket(Flags.ACK); return; } if (packet.PSH) { TCB.RcvNxt += packet.TCP_DataLength; Data = ArrayHelper.Concat(Data, packet.TCP_Data); rxBuffer.Enqueue(packet); SendEmptyPacket(Flags.ACK); return; } else if (packet.FIN) { TCB.RcvNxt++; SendEmptyPacket(Flags.ACK); WaitAndClose(); return; } if (packet.TCP_DataLength > 0 && packet.SequenceNumber >= TCB.RcvNxt) //packet sequencing { TCB.RcvNxt += packet.TCP_DataLength; Data = ArrayHelper.Concat(Data, packet.TCP_Data); } } if (packet.RST) { Status = Status.CLOSED; Global.mDebugger.Send("TCP Connection resetted!"); } else if (packet.FIN) { TCB.RcvNxt++; SendEmptyPacket(Flags.ACK); Status = Status.CLOSE_WAIT; HAL.Global.PIT.Wait(300); SendEmptyPacket(Flags.FIN); Status = Status.LAST_ACK; } }
/// <summary> /// Process SYN_SENT Status. /// </summary> /// <param name="packet">Packet to receive.</param> public void ProcessSynSent(TCPPacket packet) { if (packet.SYN) { TCB.IRS = packet.SequenceNumber; TCB.RcvNxt = packet.SequenceNumber + 1; if (packet.ACK) { TCB.SndUna = packet.AckNumber; TCB.SndWnd = packet.WindowSize; TCB.SndWl1 = packet.SequenceNumber; TCB.SndWl2 = packet.AckNumber; SendEmptyPacket(Flags.ACK); Status = Status.ESTABLISHED; } else if (packet.TCPFlags == (byte)Flags.SYN) { Status = Status.CLOSED; Global.mDebugger.Send("Simultaneous open not supported."); } else { Status = Status.CLOSED; Global.mDebugger.Send("TCP connection closed! (" + packet.getFlags() + " received on SYN_SENT state)"); } } else if (packet.ACK) { //Check for bad ACK packet if ((packet.AckNumber - TCB.ISS) < 0 || (packet.AckNumber - TCB.SndNxt) > 0) { SendEmptyPacket(Flags.RST, packet.AckNumber); Global.mDebugger.Send("Bad ACK received at SYN_SENT."); } else { TCB.RcvNxt = packet.SequenceNumber; TCB.SndNxt = packet.AckNumber; Status = Status.ESTABLISHED; } } else if (packet.FIN) { Status = Status.CLOSED; Global.mDebugger.Send("TCP connection closed! (FIN received on SYN_SENT state)."); } else if (packet.RST) { Status = Status.CLOSED; Global.mDebugger.Send("Connection refused by remote computer."); } }
/// <summary> /// Handle incoming TCP packets according to current connection status. /// </summary> /// <param name="packet">Packet to receive.</param> /// <exception cref="OverflowException">Thrown on fatal error (contact support).</exception> /// <exception cref="Sys.IO.IOException">Thrown on IO error.</exception> internal void ReceiveData(TCPPacket packet) { Global.mDebugger.Send("[" + table[(int)Status] + "] " + packet.ToString()); if (Status == Status.CLOSED) { //DO NOTHING } else if (Status == Status.LISTEN) { ProcessListen(packet); } else if (Status == Status.SYN_SENT) { ProcessSynSent(packet); } else { // Check sequence number and segment data. if (TCB.RcvNxt <= packet.SequenceNumber && packet.SequenceNumber + packet.TCP_DataLength < TCB.RcvNxt + TCB.RcvWnd) { switch (Status) { case Status.SYN_RECEIVED: ProcessSynReceived(packet); break; case Status.ESTABLISHED: ProcessEstablished(packet); break; case Status.FIN_WAIT1: ProcessFinWait1(packet); break; case Status.FIN_WAIT2: ProcessFinWait2(packet); break; case Status.CLOSE_WAIT: ProcessCloseWait(packet); break; case Status.CLOSING: ProcessClosing(packet); break; case Status.LAST_ACK: ProcessCloseWait(packet); break; case Status.TIME_WAIT: break; default: Global.mDebugger.Send("Unknown TCP connection state."); break; } } else { if (!packet.RST) { SendEmptyPacket(Flags.ACK); } Global.mDebugger.Send("Sequence number or segment data invalid, packet passed."); } } }