From 116eb9f1c18ee43a20a90fd1ecda506be7f62e53 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sat, 13 Feb 2016 20:24:15 -0800 Subject: [PATCH] Add initial ACK tracking code --- .travis.yml | 5 + CMakeLists.txt | 17 ++- include/tins/config.h.in | 3 + include/tins/exceptions.h | 11 ++ include/tins/internals.h | 3 + include/tins/tcp_ip/ack_tracker.h | 147 +++++++++++++++++++++++++ include/tins/tcp_ip/flow.h | 23 +++- include/tins/tcp_ip/stream.h | 12 +++ src/CMakeLists.txt | 1 + src/internals.cpp | 14 +++ src/tcp_ip/ack_tracker.cpp | 146 +++++++++++++++++++++++++ src/tcp_ip/flow.cpp | 35 +++--- src/tcp_ip/stream.cpp | 9 ++ tests/src/tcp_ip.cpp | 171 ++++++++++++++++++++++++++++++ 14 files changed, 578 insertions(+), 19 deletions(-) create mode 100644 include/tins/tcp_ip/ack_tracker.h create mode 100644 src/tcp_ip/ack_tracker.cpp diff --git a/.travis.yml b/.travis.yml index 54f3559..7a8e7d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ addons: packages: - libpcap-dev - libssl-dev + - libboost-all-dev before_script: - mkdir build @@ -22,5 +23,9 @@ before_script: - cmake .. -DLIBTINS_ENABLE_CXX11=1 - make tests +before_install: + - brew update + - brew install boost + script: - ctest -V \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d7d6ecd..9cdf83d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,6 @@ IF(WIN32) ADD_DEFINITIONS(-DNOMINMAX) ENDIF(WIN32) - INCLUDE(ExternalProject) # ******************* @@ -97,6 +96,22 @@ IF(LIBTINS_ENABLE_DOT11) ENDIF(LIBTINS_ENABLE_WPA2) ENDIF(LIBTINS_ENABLE_DOT11) +OPTION(LIBTINS_ENABLE_ACK_TRACKER "Enable TCP ACK tracking support" ON) +IF(LIBTINS_ENABLE_ACK_TRACKER AND HAVE_CXX11) + FIND_PACKAGE(Boost) + IF (Boost_FOUND) + MESSAGE(STATUS "Enabling TCP ACK tracking support.") + INCLUDE_DIRECTORIES(Boost_INCLUDE_DIRS) + SET(HAVE_ACK_TRACKER ON) + ELSE() + MESSAGE(WARNING "Disabling ACK tracking support as boost.icl was not found") + SET(HAVE_ACK_TRACKER OFF) + ENDIF() +ELSE() + SET(HAVE_ACK_TRACKER OFF) + MESSAGE(STATUS "Disabling ACK tracking support") +ENDIF() + # Use pcap_sendpacket to send l2 packets rather than raw sockets IF(WIN32) SET(USE_PCAP_SENDPACKET_DEFAULT ON) diff --git a/include/tins/config.h.in b/include/tins/config.h.in index 88bc5f2..ab521de 100644 --- a/include/tins/config.h.in +++ b/include/tins/config.h.in @@ -13,4 +13,7 @@ /* Use pcap_sendpacket to send l2 packets */ #cmakedefine HAVE_PACKET_SENDER_PCAP_SENDPACKET +/* Have TCP ACK tracking */ +#cmakedefine HAVE_ACK_TRACKER + #endif // TINS_CONFIG_H diff --git a/include/tins/exceptions.h b/include/tins/exceptions.h index 1b7580c..75d449f 100644 --- a/include/tins/exceptions.h +++ b/include/tins/exceptions.h @@ -200,6 +200,17 @@ public: } }; +/** + * \brief Exception thrown when a feature has been disabled + * at compile time. + */ +class feature_disabled : public exception_base { +public: + const char* what() const throw() { + return "Feature disabled"; + } +}; + /** * \brief Exception thrown when a payload is too large to fit * into a PDUOption. diff --git a/include/tins/internals.h b/include/tins/internals.h index 5ce62b0..eaed26a 100644 --- a/include/tins/internals.h +++ b/include/tins/internals.h @@ -177,6 +177,9 @@ bool decrement(HWAddress& addr) { return decrement_buffer(addr); } +// Compares sequence numbers as defined by RFC 1982. +int seq_compare(uint32_t seq1, uint32_t seq2); + IPv4Address last_address_from_mask(IPv4Address addr, IPv4Address mask); IPv6Address last_address_from_mask(IPv6Address addr, const IPv6Address& mask); template diff --git a/include/tins/tcp_ip/ack_tracker.h b/include/tins/tcp_ip/ack_tracker.h new file mode 100644 index 0000000..ae5d873 --- /dev/null +++ b/include/tins/tcp_ip/ack_tracker.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2016, Matias Fontanini + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef TINS_TCP_IP_ACK_TRACKER_H +#define TINS_TCP_IP_ACK_TRACKER_H + +#include "../config.h" + +#ifdef HAVE_ACK_TRACKER + +#include +#include + +namespace Tins { + +class PDU; + +namespace TCPIP { + +/** + * \brief Represents an acknowledged segment range + * + * The interval represented by this range is a closed interval [first, last]. + */ +class AckedRange { +public: + typedef boost::icl::discrete_interval interval_type; + + /** + * \brief Constructs an acked range + * + * \param first The first acked byte + * \param last The last acked byte (inclusive) + */ + AckedRange(uint32_t first, uint32_t last); + + /** + * \brief Gets the next acked interval in this range + * + * If has_next() == false, then this returns an empty interval + */ + interval_type next(); + + /** + * Indicates whether there is still some non-consumed acked-interval in this + * range + */ + bool has_next() const; + + /** + * Gets the first index acked by this range + */ + uint32_t first() const; + + /** + * Gets the last index acked by this range + */ + uint32_t last() const; +private: + uint32_t first_; + uint32_t last_; +}; + +/** + * \brief Allows tracking acknowledged intervals in a TCP stream + */ +class AckTracker { +public: + /** + * The type used to store ACKed intervals + */ + typedef boost::icl::interval_set interval_set_type; + + /** + * Default constructor + */ + AckTracker(); + + /** + * \brief Construct an instance using some attributes + * + * \param intial_ack The initial ACK number to use + * \param use_sack Indicate whether to use Selective ACKs to track ACK numbers + */ + AckTracker(uint32_t initial_ack, bool use_sack = true); + + /** + * \brief Process a packet + */ + void process_packet(const PDU& packet); + + /** + * \brief Indicates whether Selective ACKs should be processed + */ + void use_sack(); + + /** + * Retrieves the current ACK number in this tracker + */ + uint32_t ack_number() const; + + /** + * \brief Retrieves all acked intervals by Selective ACKs + */ + const interval_set_type& acked_intervals() const; +private: + void process_sack(const std::vector& sack); + void cleanup_sacked_intervals(uint32_t old_ack, uint32_t new_ack); + + interval_set_type acked_intervals_; + uint32_t ack_number_; + bool use_sack_; +}; + +} // TCPIP +} // Tins + +#endif // HAVE_ACK_TRACKER + +#endif // TINS_TCP_IP_ACK_TRACKER_H + diff --git a/include/tins/tcp_ip/flow.h b/include/tins/tcp_ip/flow.h index d48a1af..945ddf4 100644 --- a/include/tins/tcp_ip/flow.h +++ b/include/tins/tcp_ip/flow.h @@ -41,6 +41,7 @@ #include #include #include "../macros.h" +#include "ack_tracker.h" #include "../hw_address.h" namespace Tins { @@ -267,16 +268,31 @@ public: * \brief Indicates whether this Flow supports selective acknowledgements */ bool sack_permitted() const; + + /** + * \brief Enables tracking of ACK numbers + * + * This requires having the boost.icl library. If the library is not installed + * or ACK tracking was disabled when compiling the library, then this method + * will throw an exception. + */ + void enable_ack_tracking(); + + /** + * \brief Indicates whether ACK number tracking is enabled + */ + bool ack_tracking_enabled() const; private: // Compress all flags into just one struct using bitfields struct flags { - flags() : ignore_data_packets(0), sack_permitted(0) { + flags() : ignore_data_packets(0), sack_permitted(0), ack_tracking(0) { } uint32_t is_v6:1, ignore_data_packets:1, - sack_permitted:1; + sack_permitted:1, + ack_tracking:1; }; void store_payload(uint32_t seq, payload_type payload); @@ -295,6 +311,9 @@ private: State state_; int mss_; flags flags_; + #ifdef HAVE_ACK_TRACKER + AckTracker ack_tracker_; + #endif // HAVE_ACK_TRACKER }; } // TCPIP diff --git a/include/tins/tcp_ip/stream.h b/include/tins/tcp_ip/stream.h index 87256ee..9f4eded 100644 --- a/include/tins/tcp_ip/stream.h +++ b/include/tins/tcp_ip/stream.h @@ -334,6 +334,18 @@ public: * \param value The value to be set for this property */ void auto_cleanup_payloads(bool value); + + /** + * Enables tracking of acknowledged segments + * + * \sa Flow::enable_ack_tracking + */ + void enable_ack_tracking(); + + /** + * \brief Indicates whether ACK number tracking is enabled for this stream + */ + bool ack_tracking_enabled() const; private: static Flow extract_client_flow(const PDU& packet); static Flow extract_server_flow(const PDU& packet); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5a3da44..e906cf2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,7 @@ ADD_LIBRARY( snap.cpp sniffer.cpp tcp.cpp + tcp_ip/ack_tracker.cpp tcp_ip/flow.cpp tcp_ip/stream.cpp tcp_ip/stream_follower.cpp diff --git a/src/internals.cpp b/src/internals.cpp index d8a9541..203d0e7 100644 --- a/src/internals.cpp +++ b/src/internals.cpp @@ -349,6 +349,20 @@ bool decrement(IPv6Address& addr) { return decrement_buffer(addr); } +int seq_compare(uint32_t seq1, uint32_t seq2) { + // As defined by RFC 1982 - 2 ^ (SERIAL_BITS - 1) + static const uint32_t seq_number_diff = 2147483648U; + if (seq1 == seq2) { + return 0; + } + if (seq1 < seq2) { + return (seq2 - seq1 < seq_number_diff) ? -1 : 1; + } + else { + return (seq1 - seq2 > seq_number_diff) ? -1 : 1; + } +} + IPv4Address last_address_from_mask(IPv4Address addr, IPv4Address mask) { uint32_t addr_int = Endian::be_to_host(addr), mask_int = Endian::be_to_host(mask); diff --git a/src/tcp_ip/ack_tracker.cpp b/src/tcp_ip/ack_tracker.cpp new file mode 100644 index 0000000..9cdd9ab --- /dev/null +++ b/src/tcp_ip/ack_tracker.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2016, Matias Fontanini + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "tcp_ip/ack_tracker.h" + +#ifdef HAVE_ACK_TRACKER + +#include +#include "tcp.h" +#include "internals.h" + +using std::vector; +using std::numeric_limits; + +using Tins::Internals::seq_compare; + +namespace Tins { +namespace TCPIP { + +// AckedRange + +AckedRange::AckedRange(uint32_t first, uint32_t last) +: first_(first), last_(last) { + +} + +AckedRange::interval_type AckedRange::next() { + uint32_t interval_first = first_; + // Regular case + if (first_ <= last_) { + first_ = last_ + 1; + return interval_type::closed(interval_first, last_); + } + else { + // Range wraps around + first_ = 0; + return interval_type::closed(interval_first, numeric_limits::max()); + } +} + +bool AckedRange::has_next() const { + return seq_compare(first_, last_) <= 0; +} + +uint32_t AckedRange::first() const { + return first_; +} + +uint32_t AckedRange::last() const { + return last_; +} + +// AckTracker + +AckTracker::AckTracker() +: ack_number_(0), use_sack_(false) { + +} + +AckTracker::AckTracker(uint32_t initial_ack, bool use_sack) +: ack_number_(initial_ack), use_sack_(use_sack) { + +} + +void AckTracker::process_packet(const PDU& packet) { + const TCP* tcp = packet.find_pdu(); + if (!tcp) { + return; + } + if (seq_compare(tcp->ack_seq(), ack_number_) > 0) { + cleanup_sacked_intervals(ack_number_, tcp->ack_seq()); + ack_number_ = tcp->ack_seq(); + } + if (use_sack_) { + const TCP::option* sack_option = tcp->search_option(TCP::SACK); + if (sack_option) { + TCP::sack_type sack = sack_option->to(); + process_sack(sack); + } + } +} + +void AckTracker::process_sack(const vector& sack) { + for (size_t i = 1; i < sack.size(); i += 2) { + // Left edge must be lower than right edge + if (seq_compare(sack[i - 1], sack[i]) < 0) { + AckedRange range(sack[i - 1], sack[i] - 1); + // If this range starts after our current ack number + if (seq_compare(range.first(), ack_number_) > 0) { + while (range.has_next()) { + acked_intervals_.insert(range.next()); + } + } + } + } +} + +void AckTracker::cleanup_sacked_intervals(uint32_t old_ack, uint32_t new_ack) { + AckedRange range(old_ack, new_ack); + while (range.has_next()) { + acked_intervals_.erase(range.next()); + } +} + +void AckTracker::use_sack() { + use_sack_ = true; +} + +uint32_t AckTracker::ack_number() const { + return ack_number_; +} + +const AckTracker::interval_set_type& AckTracker::acked_intervals() const { + return acked_intervals_; +} + +} // TCPIP +} // Tins + +#endif // HAVE_ACK_TRACKER diff --git a/src/tcp_ip/flow.cpp b/src/tcp_ip/flow.cpp index 52f2df9..15f79b5 100644 --- a/src/tcp_ip/flow.cpp +++ b/src/tcp_ip/flow.cpp @@ -40,6 +40,7 @@ #include "ip.h" #include "ipv6.h" #include "rawpdu.h" +#include "internals.h" #include "exceptions.h" #include "memory_helpers.h" @@ -53,26 +54,11 @@ using std::swap; using Tins::Memory::OutputMemoryStream; using Tins::Memory::InputMemoryStream; +using Tins::Internals::seq_compare; namespace Tins { namespace TCPIP { -// As defined by RFC 1982 - 2 ^ (SERIAL_BITS - 1) -static const uint32_t seq_number_diff = 2147483648U; - -// Compares sequence numbers as defined by RFC 1982. -int seq_compare(uint32_t seq1, uint32_t seq2) { - if (seq1 == seq2) { - return 0; - } - if (seq1 < seq2) { - return (seq2 - seq1 < seq_number_diff) ? -1 : 1; - } - else { - return (seq1 - seq2 > seq_number_diff) ? -1 : 1; - } -} - Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, uint32_t sequence_number) : seq_number_(sequence_number), dest_port_(dest_port) { @@ -111,6 +97,11 @@ void Flow::process_packet(PDU& pdu) { // Update the internal state first if (tcp) { update_state(*tcp); + #ifdef HAVE_ACK_TRACKER + if (flags_.ack_tracking) { + ack_tracker_.process_packet(*tcp); + } + #endif // HAVE_ACK_TRACKER } if (flags_.ignore_data_packets) { return; @@ -316,6 +307,18 @@ bool Flow::sack_permitted() const { return flags_.sack_permitted; } +void Flow::enable_ack_tracking() { + #ifdef HAVE_ACK_TRACKER + flags_.ack_tracking = 1; + #else + throw feature_disabled(); + #endif +} + +bool Flow::ack_tracking_enabled() const { + return flags_.ack_tracking; +} + } // TCPIP } // Tins diff --git a/src/tcp_ip/stream.cpp b/src/tcp_ip/stream.cpp index 1acc742..a2f1785 100644 --- a/src/tcp_ip/stream.cpp +++ b/src/tcp_ip/stream.cpp @@ -256,6 +256,15 @@ void Stream::auto_cleanup_payloads(bool value) { auto_cleanup_ = value; } +void Stream::enable_ack_tracking() { + client_flow().enable_ack_tracking(); + server_flow().enable_ack_tracking(); +} + +bool Stream::ack_tracking_enabled() const { + return client_flow().ack_tracking_enabled() && server_flow().ack_tracking_enabled(); +} + void Stream::on_client_flow_data(const Flow& /*flow*/) { if (on_client_data_callback_) { on_client_data_callback_(*this); diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index f1fc76a..4b59c9c 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -18,6 +18,10 @@ #include "rawpdu.h" #include "packet.h" #include "utils.h" +#include "config.h" +#ifdef HAVE_ACK_TRACKER + #include "tcp_ip/ack_tracker.h" +#endif // HAVE_ACK_TRACKER using namespace std; using namespace std::chrono; @@ -508,4 +512,171 @@ TEST_F(FlowTest, StreamFollower_FollowStream) { EXPECT_EQ(payload, merge_chunks(stream_client_payload_chunks)); } + +#ifdef HAVE_ACK_TRACKER + +using namespace boost::icl; + +class AckTrackerTest : public testing::Test { +public: + typedef AckedRange::interval_type interval_type; +private: + +}; + +vector make_sack() { + return vector(); +} + +template +vector make_sack(pair head, SackEdges&&... tail) { + vector output = make_sack(tail...); + output.push_back(head.first); + output.push_back(head.second); + return output; +} + +TCP make_tcp_ack(uint32_t ack_number) { + TCP output; + output.ack_seq(ack_number); + return output; +} + +template +TCP make_tcp_ack(uint32_t ack_number, SackEdges&&... rest) { + TCP output = make_tcp_ack(ack_number); + vector sack = make_sack(rest...); + output.sack(sack); + return output; +} + + +ostream& operator<<(ostream& stream, const AckTrackerTest::interval_type& interval) { + stream << ((interval.bounds() == interval_bounds::left_open()) ? "(" : "["); + stream << interval.lower() << ", " << interval.upper(); + stream << ((interval.bounds() == interval_bounds::right_open()) ? ")" : "]"); + return stream; +} + +TEST_F(AckTrackerTest, AckedRange_1) { + AckedRange range(0, 100); + EXPECT_TRUE(range.has_next()); + EXPECT_EQ(interval_type::closed(0, 100), range.next()); + EXPECT_FALSE(range.has_next()); +} + +TEST_F(AckTrackerTest, AckedRange_2) { + AckedRange range(2, 3); + EXPECT_TRUE(range.has_next()); + EXPECT_EQ(interval_type::closed(2, 3), range.next()); + EXPECT_FALSE(range.has_next()); +} + +TEST_F(AckTrackerTest, AckedRange_3) { + AckedRange range(0, 0); + EXPECT_TRUE(range.has_next()); + EXPECT_EQ(interval_type::right_open(0, 1), range.next()); + EXPECT_FALSE(range.has_next()); +} + +TEST_F(AckTrackerTest, AckedRange_4) { + uint32_t maximum = numeric_limits::max(); + AckedRange range(maximum, maximum); + EXPECT_TRUE(range.has_next()); + EXPECT_EQ(interval_type::left_open(maximum - 1, maximum), range.next()); + EXPECT_FALSE(range.has_next()); +} + +TEST_F(AckTrackerTest, AckedRange_WrapAround) { + uint32_t first = numeric_limits::max() - 5; + AckedRange range(first, 100); + EXPECT_TRUE(range.has_next()); + EXPECT_EQ( + interval_type::closed(first, numeric_limits::max()), + range.next() + ); + EXPECT_TRUE(range.has_next()); + EXPECT_EQ(interval_type::closed(0, 100), range.next()); + EXPECT_FALSE(range.has_next()); +} + +TEST_F(AckTrackerTest, AckingTcp1) { + AckTracker tracker(0, false); + EXPECT_EQ(0, tracker.ack_number()); + tracker.process_packet(make_tcp_ack(100)); + EXPECT_EQ(100, tracker.ack_number()); + tracker.process_packet(make_tcp_ack(50)); + EXPECT_EQ(100, tracker.ack_number()); + tracker.process_packet(make_tcp_ack(150)); + EXPECT_EQ(150, tracker.ack_number()); + tracker.process_packet(make_tcp_ack(200)); + EXPECT_EQ(200, tracker.ack_number()); +} + +TEST_F(AckTrackerTest, AckingTcp2) { + uint32_t maximum = numeric_limits::max(); + AckTracker tracker(maximum - 10, false); + EXPECT_EQ(maximum - 10, tracker.ack_number()); + tracker.process_packet(make_tcp_ack(maximum - 3)); + EXPECT_EQ(maximum - 3, tracker.ack_number()); + tracker.process_packet(make_tcp_ack(maximum)); + EXPECT_EQ(maximum, tracker.ack_number()); + tracker.process_packet(make_tcp_ack(5)); + EXPECT_EQ(5, tracker.ack_number()); +} + +TEST_F(AckTrackerTest, AckingTcp3) { + uint32_t maximum = numeric_limits::max(); + AckTracker tracker(maximum - 10, false); + tracker.process_packet(make_tcp_ack(5)); + EXPECT_EQ(5, tracker.ack_number()); +} + +TEST_F(AckTrackerTest, AckingTcp_Sack1) { + AckTracker tracker(0, true); + tracker.process_packet(make_tcp_ack(0, make_pair(2, 5), make_pair(9, 11))); + EXPECT_EQ(3 + 2, tracker.acked_intervals().size()); + + tracker.process_packet(make_tcp_ack(9)); + EXPECT_EQ(1, tracker.acked_intervals().size()); + + tracker.process_packet(make_tcp_ack(15)); + EXPECT_EQ(0, tracker.acked_intervals().size()); +} + +TEST_F(AckTrackerTest, AckingTcp_Sack2) { + uint32_t maximum = numeric_limits::max(); + AckTracker tracker(maximum - 10, true); + tracker.process_packet(make_tcp_ack( + maximum - 10, + make_pair(maximum - 3, maximum), + make_pair(0, 10) + )); + EXPECT_EQ(3 + 10, tracker.acked_intervals().size()); + + tracker.process_packet(make_tcp_ack(maximum - 2)); + EXPECT_EQ(1 + 10, tracker.acked_intervals().size()); + + tracker.process_packet(make_tcp_ack(5)); + EXPECT_EQ(4, tracker.acked_intervals().size()); + + tracker.process_packet(make_tcp_ack(15)); + EXPECT_EQ(0, tracker.acked_intervals().size()); +} + +TEST_F(AckTrackerTest, AckingTcp_Sack3) { + uint32_t maximum = numeric_limits::max(); + AckTracker tracker(maximum - 10, true); + tracker.process_packet(make_tcp_ack( + maximum - 10, + make_pair(maximum - 3, 5) + )); + EXPECT_EQ(9, tracker.acked_intervals().size()); + + tracker.process_packet(make_tcp_ack(maximum)); + EXPECT_EQ(5, tracker.acked_intervals().size()); +} + +#endif // HAVE_ACK_TRACKER + #endif // TINS_IS_CXX11