diff --git a/include/tins/tcp_ip/data_tracker.h b/include/tins/tcp_ip/data_tracker.h new file mode 100644 index 0000000..774e9ad --- /dev/null +++ b/include/tins/tcp_ip/data_tracker.h @@ -0,0 +1,138 @@ +/* + * 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_DATA_TRACKER_H +#define TINS_TCP_IP_DATA_TRACKER_H + +#include +#include +#include +#include "../config.h" + +#ifdef TINS_HAVE_TCPIP + +namespace Tins { +namespace TCPIP { + +/** + * \class DataTracker + * + * Stores and tracks data in a TCP stream, reassembling segments, handling + * out of order packets, etc. + */ +class DataTracker { +public: + /** + * The type used to store the payload + */ + typedef std::vector payload_type; + + /** + * The type used to store the buffered payload + */ + typedef std::map buffered_payload_type; + + /** + * Default constructs an instance + */ + DataTracker(); + + /** + * \brief Constructs an instance using the given sequence number as the initial one + * + * \param seq_number The sequence number to use + */ + DataTracker(uint32_t seq_number); + + /** + * \brief Processes the given payload + * + * This will buffer the given data on the payload buffer or store it on the + * buffered payload map, depending the sequence number given. + * + * This method returns true iff any data was added to the payload buffer. That is + * if this method returns true, then the size of the payload will be greater than + * what it was before calling the function. + * + * \brief seq The payload's sequence number + * \brief payload The payload to process + * \return true iff any data was added to the payload buffer + */ + bool process_payload(uint32_t seq, payload_type payload); + + /** + * Retrieves the current sequence number + */ + uint32_t sequence_number() const; + + /** + * Sets the current sequence number + */ + void sequence_number(uint32_t seq); + + /** + * Retrieves the available payload (const) + */ + const payload_type& payload() const; + + /** + * Retrieves the available payload + */ + payload_type& payload(); + + /** + * Retrieves the buffered payload (const) + */ + const buffered_payload_type& buffered_payload() const; + + /** + * Retrieves the buffered payload + */ + buffered_payload_type& buffered_payload(); + + /** + * Retrieves the total amount of buffered bytes + */ + uint32_t total_buffered_bytes() const; +private: + void store_payload(uint32_t seq, payload_type payload); + buffered_payload_type::iterator erase_iterator(buffered_payload_type::iterator iter); + + payload_type payload_; + buffered_payload_type buffered_payload_; + uint32_t seq_number_; + uint32_t total_buffered_bytes_; +}; + +} // TCPIP +} // Tins + +#endif // TINS_HAVE_TCPIP + +#endif // TINS_TCP_IP_DATA_TRACKER_H diff --git a/include/tins/tcp_ip/flow.h b/include/tins/tcp_ip/flow.h index 5ea03dd..3d90562 100644 --- a/include/tins/tcp_ip/flow.h +++ b/include/tins/tcp_ip/flow.h @@ -39,9 +39,10 @@ #include #include #include +#include "../hw_address.h" #include "../macros.h" #include "ack_tracker.h" -#include "../hw_address.h" +#include "data_tracker.h" namespace Tins { @@ -86,12 +87,12 @@ public: /** * The type used to store the payload */ - typedef std::vector payload_type; + typedef DataTracker::payload_type payload_type; /** * The type used to store the buffered payload */ - typedef std::map buffered_payload_type; + typedef DataTracker::buffered_payload_type buffered_payload_type; /** * The type used to store the callback called when new data is available @@ -212,7 +213,7 @@ public: const payload_type& payload() const; /** - * Retrieves this flow's destination port + * Retrieves this flow's payload */ payload_type& payload(); @@ -306,15 +307,10 @@ private: ack_tracking:1; }; - void store_payload(uint32_t seq, payload_type payload); - buffered_payload_type::iterator erase_iterator(buffered_payload_type::iterator iter); void update_state(const TCP& tcp); void initialize(); - payload_type payload_; - buffered_payload_type buffered_payload_; - uint32_t seq_number_; - uint32_t total_buffered_bytes_; + DataTracker data_tracker_; std::array dest_address_; uint16_t dest_port_; data_available_callback_type on_data_callback_; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ed2a57a..4f362fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,7 @@ ADD_LIBRARY( tcp.cpp tcp_ip/ack_tracker.cpp tcp_ip/flow.cpp + tcp_ip/data_tracker.cpp tcp_ip/stream.cpp tcp_ip/stream_follower.cpp tcp_ip/stream_identifier.cpp diff --git a/src/tcp_ip/data_tracker.cpp b/src/tcp_ip/data_tracker.cpp new file mode 100644 index 0000000..0a5dcbc --- /dev/null +++ b/src/tcp_ip/data_tracker.cpp @@ -0,0 +1,170 @@ +/* + * 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/data_tracker.h" + +#ifdef TINS_HAVE_TCPIP + +#include "internals.h" + +using std::move; + +using Tins::Internals::seq_compare; + +namespace Tins { +namespace TCPIP { + +DataTracker::DataTracker() +: seq_number_(0), total_buffered_bytes_(0) { + +} + +DataTracker::DataTracker(uint32_t seq_number) +: seq_number_(seq_number), total_buffered_bytes_(0) { + +} + +bool DataTracker::process_payload(uint32_t seq, payload_type payload) { + const uint32_t chunk_end = seq + payload.size(); + // If the end of the chunk ends before current sequence number, ignore it. + if (seq_compare(chunk_end, seq_number_) < 0) { + return false; + } + // If it starts before our sequence number, slice it + if (seq_compare(seq, seq_number_) < 0) { + const uint32_t diff = seq_number_ - seq; + payload.erase( + payload.begin(), + payload.begin() + diff + ); + seq = seq_number_; + } + bool added_some = false; + // Store this payload + store_payload(seq, move(payload)); + // Keep looping while the fragments seq is lower or equal to our seq + buffered_payload_type::iterator iter = buffered_payload_.find(seq_number_); + while (iter != buffered_payload_.end() && seq_compare(iter->first, seq_number_) <= 0) { + // Does this fragment start before our sequence number? + if (seq_compare(iter->first, seq_number_) < 0) { + uint32_t fragment_end = iter->first + iter->second.size(); + int comparison = seq_compare(fragment_end, seq_number_); + // Does it end after our sequence number? + if (comparison > 0) { + // Then slice it + payload_type& payload = iter->second; + // First update this counter + total_buffered_bytes_ -= payload.size(); + payload.erase( + payload.begin(), + payload.begin() + (seq_number_ - iter->first) + ); + store_payload(seq_number_, move(iter->second)); + iter = erase_iterator(iter); + } + else { + // Otherwise, we've seen this part of the payload. Erase it. + iter = erase_iterator(iter); + } + } + else { + // They're equal. Add this payload. + payload_.insert( + payload_.end(), + iter->second.begin(), + iter->second.end() + ); + seq_number_ += iter->second.size(); + iter = erase_iterator(iter); + added_some = true; + } + } + return added_some; +} + +uint32_t DataTracker::sequence_number() const { + return seq_number_; +} + +void DataTracker::sequence_number(uint32_t seq) { + seq_number_ = seq; +} + +const DataTracker::payload_type& DataTracker::payload() const { + return payload_; +} + +DataTracker::payload_type& DataTracker::payload() { + return payload_; +} + +const DataTracker::buffered_payload_type& DataTracker::buffered_payload() const { + return buffered_payload_; +} + +DataTracker::buffered_payload_type& DataTracker::buffered_payload() { + return buffered_payload_; +} + +uint32_t DataTracker::total_buffered_bytes() const { + return total_buffered_bytes_; +} + +void DataTracker::store_payload(uint32_t seq, payload_type payload) { + buffered_payload_type::iterator iter = buffered_payload_.find(seq); + // New segment, store it + if (iter == buffered_payload_.end()) { + total_buffered_bytes_ += payload.size(); + buffered_payload_.insert(make_pair(seq, move(payload))); + } + else if (iter->second.size() < payload.size()) { + // Increment by the diff between sizes + total_buffered_bytes_ += (payload.size() - iter->second.size()); + // If we already have payload on this position but it's a shorter + // chunk than the new one, replace it + iter->second = move(payload); + } +} + +DataTracker::buffered_payload_type::iterator +DataTracker::erase_iterator(buffered_payload_type::iterator iter) { + buffered_payload_type::iterator output = iter; + total_buffered_bytes_ -= iter->second.size(); + ++output; + buffered_payload_.erase(iter); + if (output == buffered_payload_.end()) { + output = buffered_payload_.begin(); + } + return output; +} + +} // TCPIP +} // Tins + +#endif // TINS_HAVE_TCPIP diff --git a/src/tcp_ip/flow.cpp b/src/tcp_ip/flow.cpp index 9c415c2..5cf9f8c 100644 --- a/src/tcp_ip/flow.cpp +++ b/src/tcp_ip/flow.cpp @@ -61,7 +61,7 @@ namespace TCPIP { Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port) { +: data_tracker_(sequence_number), dest_port_(dest_port) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); flags_.is_v6 = false; @@ -70,7 +70,7 @@ Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port) { +: data_tracker_(sequence_number), dest_port_(dest_port) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); flags_.is_v6 = true; @@ -78,7 +78,6 @@ Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, } void Flow::initialize() { - total_buffered_bytes_ = 0; state_ = UNKNOWN; mss_ = -1; } @@ -110,64 +109,15 @@ void Flow::process_packet(PDU& pdu) { return; } const uint32_t chunk_end = tcp->seq() + raw->payload_size(); + const uint32_t current_seq = data_tracker_.sequence_number(); // If the end of the chunk ends after our current sequence number, process it. - if (seq_compare(chunk_end, seq_number_) >= 0) { - bool added_some = false; + if (seq_compare(chunk_end, current_seq) >= 0) { uint32_t seq = tcp->seq(); // If we're going to buffer this and we have a buffering callback, execute it - if (seq > seq_number_ && on_out_of_order_callback_) { + if (seq > current_seq && on_out_of_order_callback_) { on_out_of_order_callback_(*this, seq, raw->payload()); } - - // If it starts before our sequence number, slice it - if (seq_compare(seq, seq_number_) < 0) { - const uint32_t diff = seq_number_ - seq; - raw->payload().erase( - raw->payload().begin(), - raw->payload().begin() + diff - ); - seq = seq_number_; - } - // Store this payload - store_payload(seq, move(raw->payload())); - // Keep looping while the fragments seq is lower or equal to our seq - buffered_payload_type::iterator iter = buffered_payload_.find(seq_number_); - while (iter != buffered_payload_.end() && seq_compare(iter->first, seq_number_) <= 0) { - // Does this fragment start before our sequence number? - if (seq_compare(iter->first, seq_number_) < 0) { - uint32_t fragment_end = iter->first + iter->second.size(); - int comparison = seq_compare(fragment_end, seq_number_); - // Does it end after our sequence number? - if (comparison > 0) { - // Then slice it - payload_type& payload = iter->second; - // First update this counter - total_buffered_bytes_ -= payload.size(); - payload.erase( - payload.begin(), - payload.begin() + (seq_number_ - iter->first) - ); - store_payload(seq_number_, move(iter->second)); - iter = erase_iterator(iter); - } - else { - // Otherwise, we've seen this part of the payload. Erase it. - iter = erase_iterator(iter); - } - } - else { - // They're equal. Add this payload. - payload_.insert( - payload_.end(), - iter->second.begin(), - iter->second.end() - ); - seq_number_ += iter->second.size(); - iter = erase_iterator(iter); - added_some = true; - } - } - if (added_some) { + if (data_tracker_.process_payload(seq, move(raw->payload()))) { if (on_data_callback_) { on_data_callback_(*this); } @@ -178,33 +128,6 @@ void Flow::process_packet(PDU& pdu) { } } -void Flow::store_payload(uint32_t seq, payload_type payload) { - buffered_payload_type::iterator iter = buffered_payload_.find(seq); - // New segment, store it - if (iter == buffered_payload_.end()) { - total_buffered_bytes_ += payload.size(); - buffered_payload_.insert(make_pair(seq, move(payload))); - } - else if (iter->second.size() < payload.size()) { - // Increment by the diff between sizes - total_buffered_bytes_ += (payload.size() - iter->second.size()); - // If we already have payload on this position but it's a shorter - // chunk than the new one, replace it - iter->second = move(payload); - } -} - -Flow::buffered_payload_type::iterator Flow::erase_iterator(buffered_payload_type::iterator iter) { - buffered_payload_type::iterator output = iter; - total_buffered_bytes_ -= iter->second.size(); - ++output; - buffered_payload_.erase(iter); - if (output == buffered_payload_.end()) { - output = buffered_payload_.begin(); - } - return output; -} - void Flow::update_state(const TCP& tcp) { if ((tcp.flags() & TCP::FIN) != 0) { state_ = FIN_SENT; @@ -217,7 +140,7 @@ void Flow::update_state(const TCP& tcp) { ack_tracker_ = AckTracker(tcp.ack_seq()); #endif // TINS_HAVE_ACK_TRACKER state_ = ESTABLISHED; - seq_number_++; + data_tracker_.sequence_number(data_tracker_.sequence_number() + 1); } else if (state_ == UNKNOWN && (tcp.flags() & TCP::SYN) != 0) { // This is the server's state, sending it's first SYN|ACK @@ -225,7 +148,7 @@ void Flow::update_state(const TCP& tcp) { ack_tracker_ = AckTracker(tcp.ack_seq()); #endif // TINS_HAVE_ACK_TRACKER state_ = SYN_SENT; - seq_number_ = tcp.seq(); + data_tracker_.sequence_number(tcp.seq()); const TCP::option* mss_option = tcp.search_option(TCP::MSS); if (mss_option) { mss_ = mss_option->to(); @@ -274,7 +197,7 @@ uint16_t Flow::dport() const { } const Flow::payload_type& Flow::payload() const { - return payload_; + return data_tracker_.payload(); } Flow::State Flow::state() const { @@ -282,23 +205,23 @@ Flow::State Flow::state() const { } uint32_t Flow::sequence_number() const { - return seq_number_; + return data_tracker_.sequence_number(); } const Flow::buffered_payload_type& Flow::buffered_payload() const { - return buffered_payload_; + return data_tracker_.buffered_payload(); } Flow::buffered_payload_type& Flow::buffered_payload() { - return buffered_payload_; + return data_tracker_.buffered_payload(); } uint32_t Flow::total_buffered_bytes() const { - return total_buffered_bytes_; + return data_tracker_.total_buffered_bytes(); } Flow::payload_type& Flow::payload() { - return payload_; + return data_tracker_.payload(); } void Flow::state(State new_state) {