diff --git a/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java b/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java index f7e7aa82..a36703b7 100644 --- a/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java +++ b/pkts-streams/src/main/java/io/pkts/streams/impl/tcpFSM/TcpStreamFSM.java @@ -39,42 +39,42 @@ public enum TcpState{ init.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); init.transitionTo(ESTABLISHED).onEvent(TCPPacket.class); + handshake.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); handshake.transitionToSelf().onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); handshake.transitionTo(FIN_WAIT_1).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isFinPacket).withAction(TcpStreamFSM::setFin1); - handshake.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); handshake.transitionTo(ESTABLISHED).onEvent(TCPPacket.class); - established.transitionTo(FIN_WAIT_1).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isFinPacket).withAction(TcpStreamFSM::setFin1); established.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); established.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); // skipped the end of stream, New stream noticed + established.transitionTo(FIN_WAIT_1).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isFinPacket).withAction(TcpStreamFSM::setFin1); established.transitionToSelf().onEvent(TCPPacket.class); + finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); + finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); finWait1.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::ackOfFin1AndFin2).withAction(TcpStreamFSM::closeFin1SetFin2); // special case FIN + ACKofFin1 packet finWait1.transitionTo(FIN_WAIT_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); // if first fin has been acked finWait1.transitionTo(CLOSING_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSecondFinPacket).withAction(TcpStreamFSM::setFin2); - finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); - finWait1.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); finWait1.transitionToSelf().onEvent(TCPPacket.class); - finWait2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSecondFinPacket).withAction(TcpStreamFSM::setFin2); // 2nd fin observed finWait2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); finWait2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + finWait2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSecondFinPacket).withAction(TcpStreamFSM::setFin2); // 2nd fin observed finWait2.transitionToSelf().onEvent(TCPPacket.class); - closing1Closing2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); - closing1Closing2.transitionTo(CLOSING_1_CLOSED_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closing1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); closing1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + closing1Closing2.transitionTo(CLOSED_1_CLOSING_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); + closing1Closing2.transitionTo(CLOSING_1_CLOSED_2).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closing1Closing2.transitionToSelf().onEvent(TCPPacket.class); - closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + closed1Closing2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin2).withAction(TcpStreamFSM::closeFin2); closed1Closing2.transitionToSelf().onEvent(TCPPacket.class); - closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isRstPacket); closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isSynPacket); + closing1Closed2.transitionTo(CLOSED).onEvent(TCPPacket.class).withGuard(TcpStreamFSM::isAckOfFin1).withAction(TcpStreamFSM::closeFin1); closing1Closed2.transitionToSelf().onEvent(TCPPacket.class); diff --git a/pkts-streams/src/test/java/io/pkts/streams/impl/TcpStreamFSMTest.java b/pkts-streams/src/test/java/io/pkts/streams/impl/TcpStreamFSMTest.java new file mode 100644 index 00000000..742482dc --- /dev/null +++ b/pkts-streams/src/test/java/io/pkts/streams/impl/TcpStreamFSMTest.java @@ -0,0 +1,319 @@ +package io.pkts.streams.impl; + +import io.hektor.fsm.FSM; +import io.pkts.PacketHandler; +import io.pkts.Pcap; +import io.pkts.packet.Packet; +import io.pkts.packet.TCPPacket; +import io.pkts.protocol.Protocol; +import io.pkts.streams.StreamsTestBase; +import io.pkts.streams.impl.tcpFSM.TcpStreamContext; +import io.pkts.streams.impl.tcpFSM.TcpStreamData; +import io.pkts.streams.impl.tcpFSM.TcpStreamFSM; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; + + +public class TcpStreamFSMTest { + + FSM stream; + TcpStreamContext ctx; + TcpStreamData data; + ArrayList packets; + + @Before + public void setUp() throws Exception { + ctx = new TcpStreamContext(); + data = new TcpStreamData(); + stream = TcpStreamFSM.definition.newInstance("uuid-123",ctx, data); + stream.start(); + + + } + + + @Test + public void testFewEstablishedOnly() { + packets = retrievePackets("tcp-fsm/tcp_established_small.pcap"); + assertEquals(TcpStreamFSM.TcpState.INIT, stream.getState()); + for (TCPPacket packet : packets) { + stream.onEvent(packet); + assertEquals(TcpStreamFSM.TcpState.ESTABLISHED, stream.getState()); + } + } + + @Test + public void testEstablishedOnly() { + packets = retrievePackets("tcp-fsm/tcp_nosyn_nofin_norst.pcap"); + assertEquals(TcpStreamFSM.TcpState.INIT, stream.getState()); + for (TCPPacket packet : packets) { + stream.onEvent(packet); + assertEquals(TcpStreamFSM.TcpState.ESTABLISHED, stream.getState()); + } + } + + @Test + public void testFinEndStandard() { + packets = retrievePackets("tcp-fsm/tcp_graceful_fin1_fin2.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until start of gracefull end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED_1_CLOSING_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + @Test + public void testFinEndFinAndAckOfFin1() { + packets = retrievePackets("tcp-fsm/tcp_fin_ack.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until start of gracefull end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); // case FIN + ACK of first FIN + assertEquals(TcpStreamFSM.TcpState.CLOSED_1_CLOSING_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + + @Test + public void testFinEndSimultanuousClosing() { + packets = retrievePackets("tcp-fsm/tcp_fin_simult.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until graceful end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.CLOSING_1_CLOSING_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSING_1_CLOSED_2, stream.getState()); + + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + @Test + public void testRstEndInit() { + packets = retrievePackets("tcp-fsm/tcp_init_rst.pcap"); + + assertEquals(TcpStreamFSM.TcpState.INIT, stream.getState()); + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + @Test + public void testRstEndHanshake() { + packets = retrievePackets("tcp-fsm/tcp_handshake_rst.pcap"); + + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(2)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + } + + @Test + public void testRstEndEstablished() { + packets = retrievePackets("tcp-fsm/tcp_established_rst.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until abrupt end + for (TCPPacket packet : packets) { + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + break; + } + } + } + + @Test + public void testRstEndFinWait1() { + packets = retrievePackets("tcp-fsm/tcp_fin1_rst.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until graceful end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + // abrupt end in FIN_WAIT_1 + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + @Test + public void testRstEndFinWait2() { + packets = retrievePackets("tcp-fsm/tcp_closed1_rst2.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + packets.remove(0); + packets.remove(0); + + // process established connection until graceful end + int count = 0; + for (TCPPacket packet : packets) { + count++; + stream.onEvent(packet); + if (stream.getState() != TcpStreamFSM.TcpState.ESTABLISHED){ + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + break; + } + } + + stream.onEvent(packets.get(count)); + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_2, stream.getState()); + + // abrupt end in FIN_WAIT_2 + stream.onEvent(packets.get(++count)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + @Test + public void testSynEndEstablished() { + packets = retrievePackets("tcp-fsm/tcp_established_syn.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(2)); + assertEquals(TcpStreamFSM.TcpState.ESTABLISHED, stream.getState()); + + // new handshake end in established + stream.onEvent(packets.get(3)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + @Test + public void testSynEndFin1() { + packets = retrievePackets("tcp-fsm/tcp_fin1_syn.pcap"); + + // syn exchange + stream.onEvent(packets.get(0)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(1)); + assertEquals(TcpStreamFSM.TcpState.HANDSHAKE, stream.getState()); + stream.onEvent(packets.get(2)); + assertEquals(TcpStreamFSM.TcpState.ESTABLISHED, stream.getState()); + stream.onEvent(packets.get(3)); + assertEquals(TcpStreamFSM.TcpState.FIN_WAIT_1, stream.getState()); + // new handshake end in finWait1 + stream.onEvent(packets.get(4)); + assertEquals(TcpStreamFSM.TcpState.CLOSED, stream.getState()); + + } + + + private static ArrayList retrievePackets(String filename){ + ArrayList packets = new ArrayList<>(); + try { + Pcap pcap = Pcap.openStream(StreamsTestBase.class.getResourceAsStream(filename)); + pcap.loop(new PacketHandler() { + @Override + public boolean nextPacket(Packet packet) throws IOException { + if (packet.hasProtocol(Protocol.TCP)) { + packets.add((TCPPacket) packet.getPacket(Protocol.TCP)); + } + return true; + } + }); + + } catch (Exception e) { + e.printStackTrace(); + } + return packets; + } + +} diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_closed1_rst2.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_closed1_rst2.pcap new file mode 100644 index 00000000..7ace673b Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_closed1_rst2.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_rst.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_rst.pcap new file mode 100644 index 00000000..c714585a Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_rst.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_small.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_small.pcap new file mode 100644 index 00000000..3f30a64f Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_small.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_syn.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_syn.pcap new file mode 100644 index 00000000..3d43d39e Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_established_syn.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_rst.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_rst.pcap new file mode 100644 index 00000000..ebb4aeee Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_rst.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_syn.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_syn.pcap new file mode 100644 index 00000000..82d11aa7 Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin1_syn.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_ack.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_ack.pcap new file mode 100644 index 00000000..d46ac0b5 Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_ack.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_simult.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_simult.pcap new file mode 100644 index 00000000..ac9047be Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_fin_simult.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_graceful_fin1_fin2.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_graceful_fin1_fin2.pcap new file mode 100644 index 00000000..b3752ea0 Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_graceful_fin1_fin2.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_handshake_rst.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_handshake_rst.pcap new file mode 100644 index 00000000..eb1d4ea9 Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_handshake_rst.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_init_rst.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_init_rst.pcap new file mode 100644 index 00000000..15faa665 Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_init_rst.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_nosyn_nofin_norst.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_nosyn_nofin_norst.pcap new file mode 100644 index 00000000..296e6cb8 Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_nosyn_nofin_norst.pcap differ diff --git a/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_start_fin.pcap b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_start_fin.pcap new file mode 100644 index 00000000..f477bbd8 Binary files /dev/null and b/pkts-streams/src/test/resources/io/pkts/streams/tcp-fsm/tcp_start_fin.pcap differ