From 76b0c919b9f966374f5c02540da45e9d0ea07fb7 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sun, 7 Feb 2016 11:29:24 -0800 Subject: [PATCH 01/19] Add initial code for new TCP reassembly mechanism --- include/tins/tcp_ip.h | 210 ++++++++++++++++ src/CMakeLists.txt | 1 + src/tcp_ip.cpp | 529 +++++++++++++++++++++++++++++++++++++++ tests/src/CMakeLists.txt | 3 + tests/src/tcp_ip.cpp | 319 +++++++++++++++++++++++ 5 files changed, 1062 insertions(+) create mode 100644 include/tins/tcp_ip.h create mode 100644 src/tcp_ip.cpp create mode 100644 tests/src/tcp_ip.cpp diff --git a/include/tins/tcp_ip.h b/include/tins/tcp_ip.h new file mode 100644 index 0000000..37216a4 --- /dev/null +++ b/include/tins/tcp_ip.h @@ -0,0 +1,210 @@ +/* + * 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_H +#define TINS_TCP_IP_H + +#include "cxxstd.h" + +// This classes use C++11 features +#if TINS_IS_CXX11 + +#include +#include +#include +#include +#include +#include "macros.h" + +namespace Tins { + +class PDU; +class TCP; +class IPv4Address; +class IPv6Address; + +namespace TCPIP { + +class TINS_API TCPFlow { +public: + enum State { + UNKNOWN, + SYN_SENT, + ESTABLISHED, + FIN_SENT, + RST_SENT + }; + + typedef std::vector payload_type; + typedef std::map buffered_payload_type; + typedef std::function event_callback; + + TCPFlow(const IPv4Address& dest_address, uint16_t dest_port, + uint32_t sequence_number); + + TCPFlow(const IPv6Address& dest_address, uint16_t dest_port, + uint32_t sequence_number); + + void data_callback(const event_callback& callback); + void buffering_callback(const event_callback& callback); + + void process_packet(PDU& pdu); + + bool is_v6() const; + bool is_finished() const; + bool packet_belongs(const PDU& packet) const; + + IPv4Address dst_addr_v4() const; + IPv6Address dst_addr_v6() const; + uint16_t dport() const; + const payload_type& payload() const; + payload_type& payload(); + + void state(State new_state); + State state() const; + uint32_t sequence_number() const; +private: + void store_payload(uint32_t seq, const payload_type& payload); + buffered_payload_type::iterator erase_iterator(buffered_payload_type::iterator iter); + void update_state(const TCP& tcp); + + payload_type payload_; + buffered_payload_type buffered_payload_; + uint32_t seq_number_; + std::array dest_address_; + uint16_t dest_port_; + event_callback on_data_callback_; + event_callback on_buffering_callback_; + bool is_v6_; + State state_; +}; + +class TINS_API TCPStream { +public: + enum State { + SYN_SENT, + SYN_RCVD, + ESTABLISHED, + CLOSE_WAIT, + FIN_WAIT_1, + FIN_WAIT_2, + TIME_WAIT, + CLOSED + }; + + typedef std::function stream_callback; + + TCPStream(const PDU& initial_packet); + TCPStream(const TCPFlow& client_flow, const TCPFlow& server_flow); + + void process_packet(PDU& packet); + + TCPFlow& client_flow(); + const TCPFlow& client_flow() const; + TCPFlow& server_flow(); + const TCPFlow& server_flow() const; + + void client_data_callback(const stream_callback& callback); + void server_data_callback(const stream_callback& callback); + void client_buffering_callback(const stream_callback& callback); + void server_buffering_callback(const stream_callback& callback); + + void setup_flows_callbacks(); +private: + static TCPFlow extract_client_flow(const PDU& packet); + static TCPFlow extract_server_flow(const PDU& packet); + + + void on_client_flow_data(const TCPFlow& flow); + void on_server_flow_data(const TCPFlow& flow); + void on_client_buffering(const TCPFlow& flow); + void on_server_buffering(const TCPFlow& flow); + + TCPFlow client_flow_; + TCPFlow server_flow_; + stream_callback on_client_data_callback_; + stream_callback on_server_data_callback_; + stream_callback on_client_buffering_callback_; + stream_callback on_server_buffering_callback_; + State state_; +}; + +class TINS_API TCPStreamFollower { +public: + typedef TCPStream::stream_callback stream_callback; + + TCPStreamFollower(); + + void process_packet(PDU& packet); + + void client_data_callback(const stream_callback& callback); + void server_data_callback(const stream_callback& callback); + void client_buffering_callback(const stream_callback& callback); + void server_buffering_callback(const stream_callback& callback); + + TCPStream& find_stream(IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port); +private: + typedef std::array address_type; + + struct stream_id { + stream_id(const address_type& client_addr, uint16_t client_port, + const address_type& server_addr, uint16_t server_port); + + address_type min_address; + address_type max_address; + uint16_t min_address_port; + uint16_t max_address_port; + + bool operator<(const stream_id& rhs) const; + + static size_t hash(const stream_id& id); + }; + + typedef std::map streams_type; + + stream_id make_stream_id(const PDU& packet); + TCPStream make_stream(const PDU& packet); + static address_type serialize(IPv4Address address); + static address_type serialize(const IPv6Address& address); + + streams_type streams_; + stream_callback on_client_data_callback_; + stream_callback on_server_data_callback_; + stream_callback on_client_buffering_callback_; + stream_callback on_server_buffering_callback_; + bool attach_to_flows_; +}; + +} // TCPIP +} // Tins + +#endif // TINS_IS_CXX11 + +#endif // TINS_TCP_IP_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 66b8c50..bc3da02 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,7 @@ ADD_LIBRARY( snap.cpp sniffer.cpp tcp.cpp + tcp_ip.cpp tcp_stream.cpp udp.cpp utils.cpp diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp new file mode 100644 index 0000000..9d0573c --- /dev/null +++ b/src/tcp_ip.cpp @@ -0,0 +1,529 @@ +/* + * 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 +#include +#include "tcp_ip.h" +#include "memory.h" +#include "ip_address.h" +#include "ipv6_address.h" +#include "tcp.h" +#include "ip.h" +#include "ipv6.h" +#include "rawpdu.h" +#include "exceptions.h" +#include "memory_helpers.h" + +using std::make_pair; +using std::runtime_error; +using std::numeric_limits; +using std::max; +using std::swap; + +using Tins::Memory::OutputMemoryStream; +using Tins::Memory::InputMemoryStream; + +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; + } +} + +// TCPFlow + +TCPFlow::TCPFlow(const IPv4Address& dest_address, uint16_t dest_port, + uint32_t sequence_number) +: seq_number_(sequence_number), dest_port_(dest_port), is_v6_(false), + state_(UNKNOWN) { + OutputMemoryStream output(dest_address_.data(), dest_address_.size()); + output.write(dest_address); +} + +TCPFlow::TCPFlow(const IPv6Address& dest_address, uint16_t dest_port, + uint32_t sequence_number) +: seq_number_(sequence_number), dest_port_(dest_port), is_v6_(true), + state_(UNKNOWN) { + OutputMemoryStream output(dest_address_.data(), dest_address_.size()); + output.write(dest_address); +} + +void TCPFlow::data_callback(const event_callback& callback) { + on_data_callback_ = callback; +} + +void TCPFlow::buffering_callback(const event_callback& callback) { + on_buffering_callback_= callback; +} + +void TCPFlow::process_packet(PDU& pdu) { + TCP* tcp = pdu.find_pdu(); + RawPDU* raw = pdu.find_pdu(); + // If we sent a packet with RST or FIN on, this flow is done + if (tcp) { + update_state(*tcp); + } + if (!tcp || !raw) { + return; + } + const uint32_t chunk_end = tcp->seq() + raw->payload_size(); + // 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; + uint32_t seq = tcp->seq(); + // 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, 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; + payload.erase( + payload.begin(), + payload.begin() + (seq_number_ - iter->first) + ); + store_payload(seq_number_, 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 we don't have any other payload, we're done + if (buffered_payload_.empty()) { + break; + } + } + } + if (added_some) { + if (on_data_callback_) { + on_data_callback_(*this); + } + } + else { + if (on_buffering_callback_) { + on_buffering_callback_(*this); + } + } + } +} + +void TCPFlow::store_payload(uint32_t seq, const payload_type& payload) { + buffered_payload_type::iterator iter = buffered_payload_.find(seq); + // New segment, store it + if (iter == buffered_payload_.end()) { + buffered_payload_.insert(make_pair(seq, payload)); + } + else if (iter->second.size() < payload.size()) { + // If we already have payload on this position but it's a shorter + // chunk than the new one, replace it + iter->second = payload; + } +} + +TCPFlow::buffered_payload_type::iterator TCPFlow::erase_iterator(buffered_payload_type::iterator iter) { + buffered_payload_type::iterator output = iter; + ++output; + buffered_payload_.erase(iter); + if (output == buffered_payload_.end()) { + output = buffered_payload_.begin(); + } + return output; +} + +void TCPFlow::update_state(const TCP& tcp) { + if ((tcp.flags() & TCP::FIN) != 0) { + state_ = FIN_SENT; + } + else if ((tcp.flags() & TCP::RST) != 0) { + state_ = RST_SENT; + } + else if (state_ == SYN_SENT && (tcp.flags() & TCP::ACK) != 0) { + state_ = ESTABLISHED; + seq_number_++; + } + else if (state_ == UNKNOWN && (tcp.flags() & TCP::SYN) != 0) { + state_ = SYN_SENT; + seq_number_ = tcp.seq(); + } +} + +bool TCPFlow::is_v6() const { + return is_v6_; +} + +bool TCPFlow::is_finished() const { + return state_ == FIN_SENT || state_ == RST_SENT; +} + +bool TCPFlow::packet_belongs(const PDU& packet) const { + if (is_v6()) { + const IPv6* ip = packet.find_pdu(); + if (!ip || ip->dst_addr() != dst_addr_v6()) { + return false; + } + } + else { + const IP* ip = packet.find_pdu(); + if (!ip || ip->dst_addr() != dst_addr_v4()) { + return false; + } + } + const TCP* tcp = packet.find_pdu(); + return tcp && tcp->dport() == dport(); +} + +IPv4Address TCPFlow::dst_addr_v4() const { + InputMemoryStream stream(dest_address_.data(), dest_address_.size()); + return stream.read(); +} + +IPv6Address TCPFlow::dst_addr_v6() const { + InputMemoryStream stream(dest_address_.data(), dest_address_.size()); + return stream.read(); +} + +uint16_t TCPFlow::dport() const { + return dest_port_; +} + +const TCPFlow::payload_type& TCPFlow::payload() const { + return payload_; +} + +TCPFlow::payload_type& TCPFlow::payload() { + return payload_; +} + +void TCPFlow::state(State new_state) { + state_ = new_state; +} + +TCPFlow::State TCPFlow::state() const { + return state_; +} + +uint32_t TCPFlow::sequence_number() const { + return seq_number_; +} + +// TCPStream + +TCPStream::TCPStream(const PDU& packet) +: client_flow_(extract_client_flow(packet)), +server_flow_(extract_server_flow(packet)) { + +} + +TCPStream::TCPStream(const TCPFlow& client_flow, const TCPFlow& server_flow) +: client_flow_(client_flow), server_flow_(server_flow) { + +} + +void TCPStream::process_packet(PDU& packet) { + if (client_flow_.packet_belongs(packet)) { + client_flow_.process_packet(packet); + } + else if (server_flow_.packet_belongs(packet)) { + server_flow_.process_packet(packet); + } +} + +TCPFlow& TCPStream::client_flow() { + return client_flow_; +} + +const TCPFlow& TCPStream::client_flow() const { + return client_flow_; +} + +TCPFlow& TCPStream::server_flow() { + return server_flow_; +} + +const TCPFlow& TCPStream::server_flow() const { + return server_flow_; +} + +void TCPStream::client_data_callback(const stream_callback& callback) { + on_client_data_callback_ = callback; +} + +void TCPStream::server_data_callback(const stream_callback& callback) { + on_server_data_callback_ = callback; +} + +void TCPStream::client_buffering_callback(const stream_callback& callback) { + on_client_buffering_callback_ = callback; +} + +void TCPStream::server_buffering_callback(const stream_callback& callback) { + on_server_buffering_callback_ = callback; +} + +TCPFlow TCPStream::extract_client_flow(const PDU& packet) { + const TCP* tcp = packet.find_pdu(); + if (!tcp) { + // TODO: define proper exception + throw runtime_error("No TCP"); + } + if (const IP* ip = packet.find_pdu()) { + return TCPFlow(ip->dst_addr(), tcp->dport(), tcp->seq()); + } + else if (const IPv6* ip = packet.find_pdu()) { + return TCPFlow(ip->dst_addr(), tcp->dport(), tcp->seq()); + } + else { + // TODO: define proper exception + throw runtime_error("No valid layer 3"); + } +} + +TCPFlow TCPStream::extract_server_flow(const PDU& packet) { + const TCP* tcp = packet.find_pdu(); + if (!tcp) { + // TODO: define proper exception + throw runtime_error("No TCP"); + } + if (const IP* ip = packet.find_pdu()) { + return TCPFlow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); + } + else if (const IPv6* ip = packet.find_pdu()) { + return TCPFlow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); + } + else { + // TODO: define proper exception + throw runtime_error("No valid layer 3"); + } +} + +void TCPStream::setup_flows_callbacks() { + using std::placeholders::_1; + client_flow_.data_callback(bind(&TCPStream::on_client_flow_data, this, _1)); + server_flow_.data_callback(bind(&TCPStream::on_server_flow_data, this, _1)); + client_flow_.buffering_callback(bind(&TCPStream::on_client_buffering, this, _1)); + server_flow_.buffering_callback(bind(&TCPStream::on_server_buffering, this, _1)); +} + +void TCPStream::on_client_flow_data(const TCPFlow& flow) { + if (on_client_data_callback_) { + on_client_data_callback_(*this); + } +} + +void TCPStream::on_server_flow_data(const TCPFlow& flow) { + if (on_server_data_callback_) { + on_server_data_callback_(*this); + } +} + +void TCPStream::on_client_buffering(const TCPFlow& flow) { + if (on_client_buffering_callback_) { + on_client_buffering_callback_(*this); + } +} + +void TCPStream::on_server_buffering(const TCPFlow& flow) { + if (on_server_buffering_callback_) { + on_server_buffering_callback_(*this); + } +} + +// TCPStreamFollower + +TCPStreamFollower::TCPStreamFollower() +: attach_to_flows_(false) { + +} + +void TCPStreamFollower::process_packet(PDU& packet) { + stream_id identifier = make_stream_id(packet); + streams_type::iterator iter = streams_.find(identifier); + bool process = true; + if (iter == streams_.end()) { + const TCP& tcp = packet.rfind_pdu(); + // Start tracking if they're either SYNs or they contain data (attach + // to an already running flow). + if (tcp.flags() == TCP::SYN || (attach_to_flows_ && tcp.find_pdu() != 0)) { + iter = streams_.insert(make_pair(identifier, make_stream(packet))).first; + iter->second.setup_flows_callbacks(); + if (tcp.flags() == TCP::SYN) { + // If it's a SYN, set the proper state + iter->second.client_flow().state(TCPFlow::SYN_SENT); + process = false; + } + else { + // Otherwise, assume the connection is established + iter->second.client_flow().state(TCPFlow::ESTABLISHED); + iter->second.server_flow().state(TCPFlow::ESTABLISHED); + } + } + else { + process = false; + } + } + // We'll process it if we had already seen this stream or if we just attached to + // it and it contains payload + if (process) { + iter->second.process_packet(packet); + } +} + +void TCPStreamFollower::client_data_callback(const stream_callback& callback) { + on_client_data_callback_ = callback; +} + +void TCPStreamFollower::server_data_callback(const stream_callback& callback) { + on_server_data_callback_ = callback; +} + +void TCPStreamFollower::client_buffering_callback(const stream_callback& callback) { + on_client_buffering_callback_ = callback; +} + +void TCPStreamFollower::server_buffering_callback(const stream_callback& callback) { + on_server_buffering_callback_ = callback; +} + +TCPStream& TCPStreamFollower::find_stream(IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port) { + stream_id identifier(serialize(client_addr), client_port, + serialize(server_addr), server_port); + streams_type::iterator iter = streams_.find(identifier); + if (iter == streams_.end()) { + // TODO: define proper exception + throw runtime_error("Stream not found"); + } + else { + return iter->second; + } +} + +TCPStreamFollower::stream_id TCPStreamFollower::make_stream_id(const PDU& packet) { + const TCP* tcp = packet.find_pdu(); + if (!tcp) { + // TODO: define proper exception + throw runtime_error("No TCP"); + } + if (const IP* ip = packet.find_pdu()) { + return stream_id(serialize(ip->src_addr()), tcp->sport(), + serialize(ip->dst_addr()), tcp->dport()); + } + else if (const IPv6* ip = packet.find_pdu()) { + return stream_id(serialize(ip->src_addr()), tcp->sport(), + serialize(ip->dst_addr()), tcp->dport()); + } + else { + // TODO: define proper exception + throw runtime_error("No layer 3"); + } +} + +TCPStream TCPStreamFollower::make_stream(const PDU& packet) { + TCPStream stream(packet); + stream.client_data_callback(on_client_data_callback_); + stream.server_data_callback(on_server_data_callback_); + stream.client_buffering_callback(on_client_buffering_callback_); + stream.server_buffering_callback(on_server_buffering_callback_); + return stream; +} + +TCPStreamFollower::address_type TCPStreamFollower::serialize(IPv4Address address) { + address_type addr; + OutputMemoryStream output(addr.data(), addr.size()); + output.write(address); + return addr; +} + +TCPStreamFollower::address_type TCPStreamFollower::serialize(const IPv6Address& address) { + address_type addr; + OutputMemoryStream output(addr.data(), addr.size()); + output.write(address); + return addr; +} + +// stream_id + +TCPStreamFollower::stream_id::stream_id(const address_type& client_addr, + uint16_t client_port, + const address_type& server_addr, + uint16_t server_port) +: min_address(client_addr), max_address(server_addr), min_address_port(client_port), +max_address_port(server_port) { + if (min_address > max_address) { + swap(min_address, max_address); + swap(min_address_port, max_address_port); + } +} + +bool TCPStreamFollower::stream_id::operator<(const stream_id& rhs) const { + return tie(min_address, min_address_port, max_address, max_address_port) < + tie(rhs.min_address, rhs.min_address_port, rhs.max_address, rhs.max_address_port); +} + +} // TCPIP +} // Tins diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index 1b419b0..09779a4 100644 --- a/tests/src/CMakeLists.txt +++ b/tests/src/CMakeLists.txt @@ -83,6 +83,7 @@ ADD_CUSTOM_TARGET( SLLTest SNAPTest STPTest + TCPIPTest TCPTest TCPStreamTest UDPTest @@ -128,6 +129,7 @@ ADD_EXECUTABLE(SLLTest EXCLUDE_FROM_ALL sll.cpp) ADD_EXECUTABLE(SNAPTest EXCLUDE_FROM_ALL snap.cpp) ADD_EXECUTABLE(STPTest EXCLUDE_FROM_ALL stp.cpp) ADD_EXECUTABLE(TCPTest EXCLUDE_FROM_ALL tcp.cpp) +ADD_EXECUTABLE(TCPIPTest EXCLUDE_FROM_ALL tcp_ip.cpp) ADD_EXECUTABLE(TCPStreamTest EXCLUDE_FROM_ALL tcp_stream.cpp) ADD_EXECUTABLE(UDPTest EXCLUDE_FROM_ALL udp.cpp) ADD_EXECUTABLE(UtilsTest EXCLUDE_FROM_ALL utils.cpp) @@ -208,6 +210,7 @@ ADD_TEST(SLL SLLTest) ADD_TEST(SNAP SNAPTest) ADD_TEST(STP STPTest) ADD_TEST(TCP TCPTest) +ADD_TEST(TCPIP TCPIPTest) ADD_TEST(TCPStream TCPStreamTest) ADD_TEST(UDP UDPTest) ADD_TEST(Utils UtilsTest) diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp new file mode 100644 index 0000000..30cd17e --- /dev/null +++ b/tests/src/tcp_ip.cpp @@ -0,0 +1,319 @@ +#include "cxxstd.h" + +#if TINS_IS_CXX11 + +#include +#include +#include +#include +#include +#include +#include "tcp_ip.h" +#include "tcp.h" +#include "ip.h" +#include "ip_address.h" +#include "ipv6_address.h" +#include "ethernetII.h" +#include "rawpdu.h" +#include "utils.h" + +using namespace std; +using namespace Tins; +using namespace Tins::TCPIP; + +class TCPFlowTest : public testing::Test { +public: + struct order_element { + order_element(size_t payload_index, uint32_t payload_size) + : payload_index(payload_index),payload_size(payload_size) { + + } + + size_t payload_index; + uint32_t payload_size; + }; + + static const size_t num_packets = 20; + static EthernetII packets[], overlapped_packets1[], + overlapped_packets2[], overlapped_packets3[], + overlapped_packets4[], overlapped_packets5[]; + static const string payload; + typedef vector ordering_info_type; + + void cumulative_flow_data_handler(TCPFlow& flow); + void cumulative_stream_client_data_handler(TCPStream& stream); + void cumulative_stream_server_data_handler(TCPStream& stream); + void buffered_payload_handle(TCPFlow& session); + void run_test(uint32_t initial_seq, const ordering_info_type& chunks, + const string& payload); + void run_test(uint32_t initial_seq, const ordering_info_type& chunks); + void run_tests(const ordering_info_type& chunks, const string& payload); + void run_tests(const ordering_info_type& chunks); + ordering_info_type split_payload(const string& payload, uint32_t chunk_size); + string merge_chunks(const vector& chunks); + vector chunks_to_packets(uint32_t initial_seq, + const ordering_info_type& chunks, + const string& payload); + vector three_way_handshake(uint32_t client_seq, uint32_t server_seq, + IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port); + void set_endpoints(vector& packets, IPv4Address src_addr, + uint16_t src_port, IPv4Address dst_addr, + uint16_t dst_port); + + vector flow_payload_chunks; + vector stream_client_payload_chunks; + vector stream_server_payload_chunks; +}; + +const string TCPFlowTest::payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Sed at aliquam arcu. Sed at iaculis magna. Nam ut dolor " + "eget velit mattis posuere ut non dui. Aliquam faucibus " + "erat pretium ligula tincidunt eget tristique justo placerat. " + "Phasellus turpis tellus, ornare ultricies egestas vitae, " + "mollis sed neque. Sed et libero in nunc pharetra auctor ut " + "a eros. Mauris quis faucibus nibh. \nLorem ipsum dolor sit " + "amet, consectetur adipiscing elit. Sed at aliquam arcu. " + "Sed at iaculis magna. Nam ut dolor eget velit mattis " + "posuere ut non dui. Aliquam faucibus erat pretium ligula " + "tincidunt eget tristique justo placerat. Phasellus turpis " + "tellus, ornare ultricies egestas vitae, mollis sed neque. " + "Sed et libero in nunc pharetra auctor ut a eros. Mauris " + "quis faucibus nibh. \n\n\nCurabitur sem erat, bibendum " + "quis condimentum ut, imperdiet at est. Duis sagittis rhoncus " + "felis at ultricies. In libero urna, dignissim eu elementum " + "quis, consectetur a neque. Praesent leo sem, cursus sed lobortis " + "sit amet, ornare ac augue. Mauris tristique semper ipsum at " + "consequat. Sed fringilla dolor ut lacus sagittis quis ultricies " + "leo vulputate. Maecenas dignissim imperdiet justo. Cras libero " + "odio, vehicula et adipiscing quis, luctus vel ante. \nAliquam " + "imperdiet est quis nunc malesuada eget convallis tellus " + "ullamcorper. Vivamus ullamcorper eros sit amet odio sollicitudin " + "rutrum. Donec pellentesque faucibus nulla, ut fringilla risus " + "aliquam eget. Sed et ante mi. Morbi a turpis et tellus dapibus " + "iaculis. Etiam faucibus tellus sed metus consequat rutrum. " + "Fusce sit amet nulla massa, tempus vulputate sem. Cras tincidunt " + "quam in libero rutrum interdum. Aliquam quam sapien, facilisis " + "at vestibulum et, venenatis id mauris. Morbi rutrum gravida " + "ultricies. \nAenean et justo ut libero euismod sollicitudin. " + "Nullam enim dui, iaculis vitae bibendum et, commodo in tellus. " + "Nullam eget purus mi, a ullamcorper lorem. Suspendisse potenti. " + "Duis ac justo ut leo euismod gravida sit amet at lectus. Lorem " + "ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sed " + "arcu vitae nisi sollicitudin gravida. Nulla facilisis nibh turpis. " + "Maecenas quis imperdiet arcu. Sed sit amet nulla urna, at " + "vestibulum mauris. Suspendisse quis elit dui. Class aptent taciti " + "sociosqu ad litora torquent per conubia nostra, per inceptos " + "himenaeos. \n"; + +void TCPFlowTest::cumulative_flow_data_handler(TCPFlow& flow) { + flow_payload_chunks.push_back(flow.payload()); + flow.payload().clear(); +} + +void TCPFlowTest::cumulative_stream_client_data_handler(TCPStream& stream) { + stream_client_payload_chunks.push_back(stream.client_flow().payload()); + stream.client_flow().payload().clear(); +} + +void TCPFlowTest::cumulative_stream_server_data_handler(TCPStream& stream) { + stream_server_payload_chunks.push_back(stream.server_flow().payload()); + stream.server_flow().payload().clear(); +} + +void TCPFlowTest::buffered_payload_handle(TCPFlow& session) { + +} + +void TCPFlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks, + const string& payload) { + using std::placeholders::_1; + flow_payload_chunks.clear(); + + TCPFlow flow(IPv4Address("1.2.3.4"), 22, initial_seq); + flow.data_callback(bind(&TCPFlowTest::cumulative_flow_data_handler, this, _1)); + vector packets = chunks_to_packets(initial_seq, chunks, payload); + for (size_t i = 0; i < packets.size(); ++i) { + flow.process_packet(packets[i]); + } + string flow_payload = merge_chunks(flow_payload_chunks); + EXPECT_EQ(payload, string(flow_payload.begin(), flow_payload.end())); +} + +void TCPFlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks) { + run_test(initial_seq, chunks, payload); +} + +void TCPFlowTest::run_tests(const ordering_info_type& chunks, const string& payload) { + run_test(0, chunks, payload); + run_test(20, chunks, payload); + run_test(numeric_limits::max() / 2, chunks, payload); + run_test(numeric_limits::max() - 2, chunks, payload); + run_test(numeric_limits::max() - 5, chunks, payload); + run_test(numeric_limits::max() - 10, chunks, payload); + run_test(numeric_limits::max() - 34, chunks, payload); + run_test(numeric_limits::max() - 31, chunks, payload); +} + +void TCPFlowTest::run_tests(const ordering_info_type& chunks) { + run_tests(chunks, payload); +} + +TCPFlowTest::ordering_info_type TCPFlowTest::split_payload(const string& payload, + uint32_t chunk_size) { + ordering_info_type output; + uint32_t chunk_count = payload.size() / chunk_size; + for (uint32_t i = 0; i < chunk_count; ++i) { + output.push_back(order_element(i * chunk_size, chunk_size)); + } + if (chunk_count * chunk_size < payload.size()) { + uint32_t index = chunk_count * chunk_size; + output.push_back(order_element(index, payload.size() - index)); + } + return output; +} + +string TCPFlowTest::merge_chunks(const vector& chunks) { + string output; + for (size_t i = 0; i < chunks.size(); ++i) { + TCPFlow::payload_type this_chunk = chunks[i]; + output += string(this_chunk.begin(), this_chunk.end()); + } + return output; +} + +vector TCPFlowTest::chunks_to_packets(uint32_t initial_seq, + const ordering_info_type& chunks, + const string& payload) { + vector output; + for (size_t i = 0; i < chunks.size(); ++i) { + const order_element& element = chunks[i]; + assert(element.payload_index + element.payload_size <= payload.size()); + TCP tcp; + RawPDU raw(payload.begin() + element.payload_index, + payload.begin() + element.payload_index + element.payload_size); + tcp.seq(initial_seq + element.payload_index); + output.push_back(EthernetII() / IP() / tcp / raw); + } + return output; +} + +vector TCPFlowTest::three_way_handshake(uint32_t client_seq, uint32_t server_seq, + IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port) { + vector output; + output.push_back(EthernetII() / IP(server_addr, client_addr) / TCP(server_port, client_port)); + output.push_back(EthernetII() / IP(client_addr, server_addr) / TCP(client_port, server_port)); + output.push_back(EthernetII() / IP(server_addr, client_addr) / TCP(server_port, client_port)); + output[0].rfind_pdu().flags(TCP::SYN); + output[0].rfind_pdu().seq(client_seq); + output[1].rfind_pdu().flags(TCP::SYN | TCP::ACK); + output[1].rfind_pdu().seq(server_seq); + output[1].rfind_pdu().ack_seq(client_seq + 1); + output[2].rfind_pdu().flags(TCP::ACK); + output[2].rfind_pdu().seq(client_seq + 1); + output[2].rfind_pdu().ack_seq(server_seq + 1); + return output; +} + +void TCPFlowTest::set_endpoints(vector& packets, IPv4Address src_addr, + uint16_t src_port, IPv4Address dst_addr, + uint16_t dst_port) { + for (size_t i = 0; i < packets.size(); ++i) { + packets[i].rfind_pdu().src_addr(src_addr); + packets[i].rfind_pdu().dst_addr(dst_addr); + packets[i].rfind_pdu().sport(src_port); + packets[i].rfind_pdu().dport(dst_port); + } +} + +TEST_F(TCPFlowTest, ReassembleStreamPlain) { + ordering_info_type chunks = split_payload(payload, 5); + run_tests(chunks); +} + +TEST_F(TCPFlowTest, ReassembleStreamReordering) { + ordering_info_type chunks = split_payload(payload, 5); + // e.g. input [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + // after this it's [2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8] + for (size_t i = 0; i < chunks.size(); i += 4) { + if (i + 2 < chunks.size()) { + swap(chunks[i], chunks[i + 2]); + } + } + run_tests(chunks); +} + +TEST_F(TCPFlowTest, ReassembleStreamReversed) { + ordering_info_type chunks = split_payload(payload, 5); + reverse(chunks.begin(), chunks.end()); + run_tests(chunks); +} + +TEST_F(TCPFlowTest, Overlapping) { + string payload = "Hello world. This is a payload"; + ordering_info_type chunks; + // "Hello " + chunks.push_back(order_element(0, 6)); + // "ello Wo" + chunks.push_back(order_element(1, 7)); + // "lo World" + chunks.push_back(order_element(3, 8)); + chunks.push_back(order_element(10, payload.size() - 10)); + chunks.push_back(order_element(9, 1)); + run_tests(chunks, payload); + + reverse(chunks.begin(), chunks.end()); + run_tests(chunks, payload); + + swap(chunks[2], chunks[4]); + run_tests(chunks, payload); +} + +TEST_F(TCPFlowTest, TCPStreamFollower_ThreeWayHandshake) { + using std::placeholders::_1; + + vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + TCPStreamFollower follower; + follower.client_data_callback(bind(&TCPFlowTest::cumulative_stream_client_data_handler, + this, _1)); + for (size_t i = 0; i < packets.size(); ++i) { + follower.process_packet(packets[i]); + } + TCPStream& stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); + EXPECT_EQ(TCPFlow::ESTABLISHED, stream.client_flow().state()); + EXPECT_EQ(TCPFlow::SYN_SENT, stream.server_flow().state()); + EXPECT_EQ(30, stream.client_flow().sequence_number()); + EXPECT_EQ(60, stream.server_flow().sequence_number()); + EXPECT_EQ(IPv4Address("4.3.2.1"), stream.client_flow().dst_addr_v4()); + EXPECT_EQ(25, stream.client_flow().dport()); + EXPECT_EQ(IPv4Address("1.2.3.4"), stream.server_flow().dst_addr_v4()); + EXPECT_EQ(22, stream.server_flow().dport()); + + IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); + server_packet.rfind_pdu().flags(TCP::ACK); + follower.process_packet(server_packet); + + EXPECT_EQ(TCPFlow::ESTABLISHED, stream.server_flow().state()); + EXPECT_EQ(61, stream.server_flow().sequence_number()); +} + +TEST_F(TCPFlowTest, TCPStreamFollower_FollowStream) { + using std::placeholders::_1; + + vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + ordering_info_type chunks = split_payload(payload, 5); + vector chunk_packets = chunks_to_packets(30 /*initial_seq*/, chunks, payload); + set_endpoints(chunk_packets, "1.2.3.4", 22, "4.3.2.1", 25); + packets.insert(packets.end(), chunk_packets.begin(), chunk_packets.end()); + TCPStreamFollower follower; + follower.client_data_callback(bind(&TCPFlowTest::cumulative_stream_client_data_handler, + this, _1)); + for (size_t i = 0; i < packets.size(); ++i) { + follower.process_packet(packets[i]); + } + EXPECT_EQ(chunk_packets.size(), stream_client_payload_chunks.size()); + EXPECT_EQ(payload, merge_chunks(stream_client_payload_chunks)); +} + +#endif // TINS_IS_CXX11 From 07b5d74179d64b80f15a01fca0521acf822f3c3e Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sun, 7 Feb 2016 16:30:37 -0800 Subject: [PATCH 02/19] Refactor TCP stream code and add http_dump example --- examples/CMakeLists.txt | 2 + examples/http_dump.cpp | 157 ++++++++++++++++++++++++ include/tins/tcp_ip.h | 94 +++++++------- src/tcp_ip.cpp | 262 +++++++++++++++++++++++++--------------- tests/src/tcp_ip.cpp | 247 +++++++++++++++++++++++-------------- 5 files changed, 536 insertions(+), 226 deletions(-) create mode 100644 examples/http_dump.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4e3a15f..8baf71e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,6 +15,7 @@ IF(libtins_FOUND) dns_queries dns_spoof dns_stats + http_dump icmp_responses interfaces_info tcp_connection_close @@ -40,6 +41,7 @@ IF(libtins_FOUND) ADD_EXECUTABLE(arpmonitor EXCLUDE_FROM_ALL arpmonitor.cpp) ADD_EXECUTABLE(dns_queries EXCLUDE_FROM_ALL dns_queries.cpp) ADD_EXECUTABLE(dns_spoof EXCLUDE_FROM_ALL dns_spoof.cpp) + ADD_EXECUTABLE(http_dump EXCLUDE_FROM_ALL http_dump.cpp) ADD_EXECUTABLE(icmp_responses EXCLUDE_FROM_ALL icmp_responses.cpp) ADD_EXECUTABLE(interfaces_info EXCLUDE_FROM_ALL interfaces_info.cpp) ADD_EXECUTABLE(tcp_connection_close EXCLUDE_FROM_ALL tcp_connection_close.cpp) diff --git a/examples/http_dump.cpp b/examples/http_dump.cpp new file mode 100644 index 0000000..dcd2c8f --- /dev/null +++ b/examples/http_dump.cpp @@ -0,0 +1,157 @@ +/* + * 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 +#include +#include "tins/tcp_ip.h" +#include "tins/sniffer.h" +#include "tins/ip_address.h" +#include "tins/ipv6_address.h" + +using std::cout; +using std::cerr; +using std::endl; +using std::bind; +using std::string; +using std::ostringstream; +using std::exception; + +using Tins::Sniffer; +using Tins::SnifferConfiguration; +using Tins::TCPIP::StreamFollower; +using Tins::TCPIP::Stream; + +// Convert the client endpoint to a readable string +string client_endpoint(const Stream& stream) { + ostringstream output; + // Use the IPv4 or IPv6 address depending on which protocol the + // connection uses + if (stream.is_v6()) { + output << stream.client_addr_v6(); + } + else { + output << stream.client_addr_v4(); + } + output << ":" << stream.client_port(); + return output.str(); +} + +// Convert the server endpoint to a readable string +string server_endpoint(const Stream& stream) { + ostringstream output; + if (stream.is_v6()) { + output << stream.server_addr_v6(); + } + else { + output << stream.server_addr_v4(); + } + output << ":" << stream.server_port(); + return output.str(); +} + +// Concat both endpoints to get a readable stream identifier +string stream_identifier(const Stream& stream) { + ostringstream output; + output << client_endpoint(stream) << " - " << server_endpoint(stream); + return output.str(); +} + +// Whenever there's new client data on the stream, this callback is executed. +void on_client_data(Stream& stream) { + // Construct a string out of the contents of the client's payload + string data(stream.client_payload().begin(), stream.client_payload().end()); + // Now print it, prepending some information about the stream + cout << client_endpoint(stream) << " >> " + << server_endpoint(stream) << ": " << endl << data << endl; + // Now erase the stored data, as we've already processed it. This is important, + // since if we don't do this, the connection will keep buffering data until + // the stream is closed + stream.client_payload().clear(); +} + +// Whenever there's new server data on the stream, this callback is executed. +// This does the same thing as on_client_data +void on_server_data(Stream& stream) { + string data(stream.server_payload().begin(), stream.server_payload().end()); + cout << server_endpoint(stream) << " >> " + << client_endpoint(stream) << ": " << endl << data << endl; + stream.server_payload().clear(); +} + +// When a connection is closed, this callback is executed. +void on_connection_closed(Stream& stream) { + cout << "[+] Connection closed: " << stream_identifier(stream) << endl; +} + +// When a new connection is captured, this callback will be executed. +void on_new_connection(Stream& stream) { + // Print some information about the new connection + cout << "[+] New connection " << stream_identifier(stream) << endl; + // Now configure the callbacks on it. + // First, we want on_client_data to be called every time there's new client data + stream.client_data_callback(&on_client_data); + // Same thing for server data, but calling on_server_data + stream.server_data_callback(&on_server_data); + // When the connection is closed, call on_connection_closed + stream.stream_closed_callback(&on_connection_closed); +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + cout << "Usage: " << argv[0] << " " << endl; + return 1; + } + using std::placeholders::_1; + + try { + // Construct the sniffer configuration object + SnifferConfiguration config; + // Only capture TCP traffic sent from/to port 80 + config.set_filter("tcp port 80"); + // Construct the sniffer we'll use + Sniffer sniffer(argv[1], config); + + cout << "Starting capture on interface " << argv[1] << endl; + + // Now construct the stream follower + StreamFollower follower; + // We just need to specify the callback to be executed when a new + // stream is captured. In this stream, you should define which callbacks + // will be executed whenever new data is sent on that stream + // (see on_new_connection) + follower.new_stream_callback(&on_new_connection); + // Now start capturing. Every time there's a new packet, call + // follower.process_packet + sniffer.sniff_loop(bind(&StreamFollower::process_packet, &follower, _1)); + } + catch (exception& ex) { + cerr << "Error: " << ex.what() << endl; + return 1; + } +} diff --git a/include/tins/tcp_ip.h b/include/tins/tcp_ip.h index 37216a4..2d3b110 100644 --- a/include/tins/tcp_ip.h +++ b/include/tins/tcp_ip.h @@ -51,7 +51,7 @@ class IPv6Address; namespace TCPIP { -class TINS_API TCPFlow { +class TINS_API Flow { public: enum State { UNKNOWN, @@ -63,13 +63,13 @@ public: typedef std::vector payload_type; typedef std::map buffered_payload_type; - typedef std::function event_callback; + typedef std::function event_callback; - TCPFlow(const IPv4Address& dest_address, uint16_t dest_port, - uint32_t sequence_number); + Flow(const IPv4Address& dest_address, uint16_t dest_port, + uint32_t sequence_number); - TCPFlow(const IPv6Address& dest_address, uint16_t dest_port, - uint32_t sequence_number); + Flow(const IPv6Address& dest_address, uint16_t dest_port, + uint32_t sequence_number); void data_callback(const event_callback& callback); void buffering_callback(const event_callback& callback); @@ -85,10 +85,12 @@ public: uint16_t dport() const; const payload_type& payload() const; payload_type& payload(); - - void state(State new_state); State state() const; uint32_t sequence_number() const; + const buffered_payload_type& buffered_payload() const; + buffered_payload_type& buffered_payload(); + + void state(State new_state); private: void store_payload(uint32_t seq, const payload_type& payload); buffered_payload_type::iterator erase_iterator(buffered_payload_type::iterator iter); @@ -105,7 +107,7 @@ private: State state_; }; -class TINS_API TCPStream { +class TINS_API Stream { public: enum State { SYN_SENT, @@ -118,18 +120,34 @@ public: CLOSED }; - typedef std::function stream_callback; + typedef std::function stream_callback; + typedef Flow::payload_type payload_type; - TCPStream(const PDU& initial_packet); - TCPStream(const TCPFlow& client_flow, const TCPFlow& server_flow); + Stream(const PDU& initial_packet); + Stream(const Flow& client_flow, const Flow& server_flow); void process_packet(PDU& packet); - TCPFlow& client_flow(); - const TCPFlow& client_flow() const; - TCPFlow& server_flow(); - const TCPFlow& server_flow() const; + Flow& client_flow(); + const Flow& client_flow() const; + Flow& server_flow(); + const Flow& server_flow() const; + bool is_finished() const; + bool is_v6() const; + + IPv4Address client_addr_v4() const; + IPv6Address client_addr_v6() const; + IPv4Address server_addr_v4() const; + IPv6Address server_addr_v6() const; + uint16_t client_port() const; + uint16_t server_port() const; + const payload_type& client_payload() const; + payload_type& client_payload(); + const payload_type& server_payload() const; + payload_type& server_payload(); + + void stream_closed_callback(const stream_callback& callback); void client_data_callback(const stream_callback& callback); void server_data_callback(const stream_callback& callback); void client_buffering_callback(const stream_callback& callback); @@ -137,17 +155,17 @@ public: void setup_flows_callbacks(); private: - static TCPFlow extract_client_flow(const PDU& packet); - static TCPFlow extract_server_flow(const PDU& packet); + static Flow extract_client_flow(const PDU& packet); + static Flow extract_server_flow(const PDU& packet); + void on_client_flow_data(const Flow& flow); + void on_server_flow_data(const Flow& flow); + void on_client_buffering(const Flow& flow); + void on_server_buffering(const Flow& flow); - void on_client_flow_data(const TCPFlow& flow); - void on_server_flow_data(const TCPFlow& flow); - void on_client_buffering(const TCPFlow& flow); - void on_server_buffering(const TCPFlow& flow); - - TCPFlow client_flow_; - TCPFlow server_flow_; + Flow client_flow_; + Flow server_flow_; + stream_callback on_stream_closed_; stream_callback on_client_data_callback_; stream_callback on_server_data_callback_; stream_callback on_client_buffering_callback_; @@ -155,22 +173,19 @@ private: State state_; }; -class TINS_API TCPStreamFollower { +class TINS_API StreamFollower { public: - typedef TCPStream::stream_callback stream_callback; + typedef Stream::stream_callback stream_callback; - TCPStreamFollower(); + StreamFollower(); - void process_packet(PDU& packet); + bool process_packet(PDU& packet); + void new_stream_callback(const stream_callback& callback); - void client_data_callback(const stream_callback& callback); - void server_data_callback(const stream_callback& callback); - void client_buffering_callback(const stream_callback& callback); - void server_buffering_callback(const stream_callback& callback); - - TCPStream& find_stream(IPv4Address client_addr, uint16_t client_port, + Stream& find_stream(IPv4Address client_addr, uint16_t client_port, IPv4Address server_addr, uint16_t server_port); private: + static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; typedef std::array address_type; struct stream_id { @@ -187,18 +202,15 @@ private: static size_t hash(const stream_id& id); }; - typedef std::map streams_type; + typedef std::map streams_type; stream_id make_stream_id(const PDU& packet); - TCPStream make_stream(const PDU& packet); static address_type serialize(IPv4Address address); static address_type serialize(const IPv6Address& address); streams_type streams_; - stream_callback on_client_data_callback_; - stream_callback on_server_data_callback_; - stream_callback on_client_buffering_callback_; - stream_callback on_server_buffering_callback_; + stream_callback on_new_connection_; + size_t max_buffered_chunks_; bool attach_to_flows_; }; diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp index 9d0573c..8e90f36 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip.cpp @@ -68,33 +68,33 @@ int seq_compare(uint32_t seq1, uint32_t seq2) { } } -// TCPFlow +// Flow -TCPFlow::TCPFlow(const IPv4Address& dest_address, uint16_t dest_port, - uint32_t sequence_number) +Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, + uint32_t sequence_number) : seq_number_(sequence_number), dest_port_(dest_port), is_v6_(false), state_(UNKNOWN) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); } -TCPFlow::TCPFlow(const IPv6Address& dest_address, uint16_t dest_port, - uint32_t sequence_number) +Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, + uint32_t sequence_number) : seq_number_(sequence_number), dest_port_(dest_port), is_v6_(true), state_(UNKNOWN) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); } -void TCPFlow::data_callback(const event_callback& callback) { +void Flow::data_callback(const event_callback& callback) { on_data_callback_ = callback; } -void TCPFlow::buffering_callback(const event_callback& callback) { +void Flow::buffering_callback(const event_callback& callback) { on_buffering_callback_= callback; } -void TCPFlow::process_packet(PDU& pdu) { +void Flow::process_packet(PDU& pdu) { TCP* tcp = pdu.find_pdu(); RawPDU* raw = pdu.find_pdu(); // If we sent a packet with RST or FIN on, this flow is done @@ -172,7 +172,7 @@ void TCPFlow::process_packet(PDU& pdu) { } } -void TCPFlow::store_payload(uint32_t seq, const payload_type& payload) { +void Flow::store_payload(uint32_t seq, const payload_type& payload) { buffered_payload_type::iterator iter = buffered_payload_.find(seq); // New segment, store it if (iter == buffered_payload_.end()) { @@ -185,7 +185,7 @@ void TCPFlow::store_payload(uint32_t seq, const payload_type& payload) { } } -TCPFlow::buffered_payload_type::iterator TCPFlow::erase_iterator(buffered_payload_type::iterator iter) { +Flow::buffered_payload_type::iterator Flow::erase_iterator(buffered_payload_type::iterator iter) { buffered_payload_type::iterator output = iter; ++output; buffered_payload_.erase(iter); @@ -195,7 +195,7 @@ TCPFlow::buffered_payload_type::iterator TCPFlow::erase_iterator(buffered_payloa return output; } -void TCPFlow::update_state(const TCP& tcp) { +void Flow::update_state(const TCP& tcp) { if ((tcp.flags() & TCP::FIN) != 0) { state_ = FIN_SENT; } @@ -212,15 +212,15 @@ void TCPFlow::update_state(const TCP& tcp) { } } -bool TCPFlow::is_v6() const { +bool Flow::is_v6() const { return is_v6_; } -bool TCPFlow::is_finished() const { +bool Flow::is_finished() const { return state_ == FIN_SENT || state_ == RST_SENT; } -bool TCPFlow::packet_belongs(const PDU& packet) const { +bool Flow::packet_belongs(const PDU& packet) const { if (is_v6()) { const IPv6* ip = packet.find_pdu(); if (!ip || ip->dst_addr() != dst_addr_v6()) { @@ -237,105 +237,177 @@ bool TCPFlow::packet_belongs(const PDU& packet) const { return tcp && tcp->dport() == dport(); } -IPv4Address TCPFlow::dst_addr_v4() const { +IPv4Address Flow::dst_addr_v4() const { InputMemoryStream stream(dest_address_.data(), dest_address_.size()); return stream.read(); } -IPv6Address TCPFlow::dst_addr_v6() const { +IPv6Address Flow::dst_addr_v6() const { InputMemoryStream stream(dest_address_.data(), dest_address_.size()); return stream.read(); } -uint16_t TCPFlow::dport() const { +uint16_t Flow::dport() const { return dest_port_; } -const TCPFlow::payload_type& TCPFlow::payload() const { +const Flow::payload_type& Flow::payload() const { return payload_; } -TCPFlow::payload_type& TCPFlow::payload() { - return payload_; -} - -void TCPFlow::state(State new_state) { - state_ = new_state; -} - -TCPFlow::State TCPFlow::state() const { +Flow::State Flow::state() const { return state_; } -uint32_t TCPFlow::sequence_number() const { +uint32_t Flow::sequence_number() const { return seq_number_; } -// TCPStream +const Flow::buffered_payload_type& Flow::buffered_payload() const { + return buffered_payload_; +} -TCPStream::TCPStream(const PDU& packet) +Flow::buffered_payload_type& Flow::buffered_payload() { + return buffered_payload_; +} + +Flow::payload_type& Flow::payload() { + return payload_; +} + +void Flow::state(State new_state) { + state_ = new_state; +} + +// Stream + +Stream::Stream(const PDU& packet) : client_flow_(extract_client_flow(packet)), server_flow_(extract_server_flow(packet)) { } -TCPStream::TCPStream(const TCPFlow& client_flow, const TCPFlow& server_flow) +Stream::Stream(const Flow& client_flow, const Flow& server_flow) : client_flow_(client_flow), server_flow_(server_flow) { } -void TCPStream::process_packet(PDU& packet) { +void Stream::process_packet(PDU& packet) { if (client_flow_.packet_belongs(packet)) { client_flow_.process_packet(packet); } else if (server_flow_.packet_belongs(packet)) { server_flow_.process_packet(packet); } + if (is_finished() && on_stream_closed_) { + on_stream_closed_(*this); + } } -TCPFlow& TCPStream::client_flow() { +Flow& Stream::client_flow() { return client_flow_; } -const TCPFlow& TCPStream::client_flow() const { +const Flow& Stream::client_flow() const { return client_flow_; } -TCPFlow& TCPStream::server_flow() { +Flow& Stream::server_flow() { return server_flow_; } -const TCPFlow& TCPStream::server_flow() const { +const Flow& Stream::server_flow() const { return server_flow_; } -void TCPStream::client_data_callback(const stream_callback& callback) { +void Stream::stream_closed_callback(const stream_callback& callback) { + on_stream_closed_ = callback; +} + +void Stream::client_data_callback(const stream_callback& callback) { on_client_data_callback_ = callback; } -void TCPStream::server_data_callback(const stream_callback& callback) { +void Stream::server_data_callback(const stream_callback& callback) { on_server_data_callback_ = callback; } -void TCPStream::client_buffering_callback(const stream_callback& callback) { +void Stream::client_buffering_callback(const stream_callback& callback) { on_client_buffering_callback_ = callback; } -void TCPStream::server_buffering_callback(const stream_callback& callback) { +void Stream::server_buffering_callback(const stream_callback& callback) { on_server_buffering_callback_ = callback; } -TCPFlow TCPStream::extract_client_flow(const PDU& packet) { +bool Stream::is_finished() const { + const Flow::State client_state = client_flow_.state(); + const Flow::State server_state = server_flow_.state(); + // If either peer sent a RST then the stream is done + if (client_state == Flow::RST_SENT || server_state == Flow::RST_SENT) { + return true; + } + else { + // Otherwise, only finish if both sent a FIN + return client_state == Flow::FIN_SENT && server_state == Flow::FIN_SENT; + } +} + +bool Stream::is_v6() const { + return server_flow().is_v6(); +} + +IPv4Address Stream::client_addr_v4() const { + return server_flow().dst_addr_v4(); +} + +IPv6Address Stream::client_addr_v6() const { + return server_flow().dst_addr_v6(); +} + +IPv4Address Stream::server_addr_v4() const { + return client_flow().dst_addr_v4(); +} + +IPv6Address Stream::server_addr_v6() const { + return client_flow().dst_addr_v6(); +} + +uint16_t Stream::client_port() const { + return server_flow().dport(); +} + +uint16_t Stream::server_port() const { + return client_flow().dport(); +} + +const Stream::payload_type& Stream::client_payload() const { + return client_flow().payload(); +} + +Stream::payload_type& Stream::client_payload() { + return client_flow().payload(); +} + +const Stream::payload_type& Stream::server_payload() const { + return server_flow().payload(); +} + +Stream::payload_type& Stream::server_payload() { + return server_flow().payload(); +} + +Flow Stream::extract_client_flow(const PDU& packet) { const TCP* tcp = packet.find_pdu(); if (!tcp) { // TODO: define proper exception throw runtime_error("No TCP"); } if (const IP* ip = packet.find_pdu()) { - return TCPFlow(ip->dst_addr(), tcp->dport(), tcp->seq()); + return Flow(ip->dst_addr(), tcp->dport(), tcp->seq()); } else if (const IPv6* ip = packet.find_pdu()) { - return TCPFlow(ip->dst_addr(), tcp->dport(), tcp->seq()); + return Flow(ip->dst_addr(), tcp->dport(), tcp->seq()); } else { // TODO: define proper exception @@ -343,17 +415,17 @@ TCPFlow TCPStream::extract_client_flow(const PDU& packet) { } } -TCPFlow TCPStream::extract_server_flow(const PDU& packet) { +Flow Stream::extract_server_flow(const PDU& packet) { const TCP* tcp = packet.find_pdu(); if (!tcp) { // TODO: define proper exception throw runtime_error("No TCP"); } if (const IP* ip = packet.find_pdu()) { - return TCPFlow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); + return Flow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); } else if (const IPv6* ip = packet.find_pdu()) { - return TCPFlow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); + return Flow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); } else { // TODO: define proper exception @@ -361,46 +433,49 @@ TCPFlow TCPStream::extract_server_flow(const PDU& packet) { } } -void TCPStream::setup_flows_callbacks() { +void Stream::setup_flows_callbacks() { using std::placeholders::_1; - client_flow_.data_callback(bind(&TCPStream::on_client_flow_data, this, _1)); - server_flow_.data_callback(bind(&TCPStream::on_server_flow_data, this, _1)); - client_flow_.buffering_callback(bind(&TCPStream::on_client_buffering, this, _1)); - server_flow_.buffering_callback(bind(&TCPStream::on_server_buffering, this, _1)); + client_flow_.data_callback(bind(&Stream::on_client_flow_data, this, _1)); + server_flow_.data_callback(bind(&Stream::on_server_flow_data, this, _1)); + client_flow_.buffering_callback(bind(&Stream::on_client_buffering, this, _1)); + server_flow_.buffering_callback(bind(&Stream::on_server_buffering, this, _1)); } -void TCPStream::on_client_flow_data(const TCPFlow& flow) { + +void Stream::on_client_flow_data(const Flow& flow) { if (on_client_data_callback_) { on_client_data_callback_(*this); } } -void TCPStream::on_server_flow_data(const TCPFlow& flow) { +void Stream::on_server_flow_data(const Flow& flow) { if (on_server_data_callback_) { on_server_data_callback_(*this); } } -void TCPStream::on_client_buffering(const TCPFlow& flow) { +void Stream::on_client_buffering(const Flow& flow) { if (on_client_buffering_callback_) { on_client_buffering_callback_(*this); } } -void TCPStream::on_server_buffering(const TCPFlow& flow) { +void Stream::on_server_buffering(const Flow& flow) { if (on_server_buffering_callback_) { on_server_buffering_callback_(*this); } } -// TCPStreamFollower +// StreamFollower -TCPStreamFollower::TCPStreamFollower() -: attach_to_flows_(false) { +const size_t StreamFollower::DEFAULT_MAX_BUFFERED_CHUNKS = 512; + +StreamFollower::StreamFollower() +: max_buffered_chunks_(DEFAULT_MAX_BUFFERED_CHUNKS), attach_to_flows_(false) { } -void TCPStreamFollower::process_packet(PDU& packet) { +bool StreamFollower::process_packet(PDU& packet) { stream_id identifier = make_stream_id(packet); streams_type::iterator iter = streams_.find(identifier); bool process = true; @@ -409,17 +484,24 @@ void TCPStreamFollower::process_packet(PDU& packet) { // Start tracking if they're either SYNs or they contain data (attach // to an already running flow). if (tcp.flags() == TCP::SYN || (attach_to_flows_ && tcp.find_pdu() != 0)) { - iter = streams_.insert(make_pair(identifier, make_stream(packet))).first; + iter = streams_.insert(make_pair(identifier, Stream(packet))).first; iter->second.setup_flows_callbacks(); + if (on_new_connection_) { + on_new_connection_(iter->second); + } + else { + // TODO: use proper exception + throw runtime_error("No new connection callback set"); + } if (tcp.flags() == TCP::SYN) { // If it's a SYN, set the proper state - iter->second.client_flow().state(TCPFlow::SYN_SENT); + iter->second.client_flow().state(Flow::SYN_SENT); process = false; } else { // Otherwise, assume the connection is established - iter->second.client_flow().state(TCPFlow::ESTABLISHED); - iter->second.server_flow().state(TCPFlow::ESTABLISHED); + iter->second.client_flow().state(Flow::ESTABLISHED); + iter->second.server_flow().state(Flow::ESTABLISHED); } } else { @@ -429,28 +511,23 @@ void TCPStreamFollower::process_packet(PDU& packet) { // We'll process it if we had already seen this stream or if we just attached to // it and it contains payload if (process) { - iter->second.process_packet(packet); + Stream& stream = iter->second; + stream.process_packet(packet); + size_t total_chunks = stream.client_flow().buffered_payload().size() + + stream.server_flow().buffered_payload().size(); + if (stream.is_finished() || total_chunks > max_buffered_chunks_) { + streams_.erase(iter); + } } + return true; } -void TCPStreamFollower::client_data_callback(const stream_callback& callback) { - on_client_data_callback_ = callback; +void StreamFollower::new_stream_callback(const stream_callback& callback) { + on_new_connection_ = callback; } -void TCPStreamFollower::server_data_callback(const stream_callback& callback) { - on_server_data_callback_ = callback; -} - -void TCPStreamFollower::client_buffering_callback(const stream_callback& callback) { - on_client_buffering_callback_ = callback; -} - -void TCPStreamFollower::server_buffering_callback(const stream_callback& callback) { - on_server_buffering_callback_ = callback; -} - -TCPStream& TCPStreamFollower::find_stream(IPv4Address client_addr, uint16_t client_port, - IPv4Address server_addr, uint16_t server_port) { +Stream& StreamFollower::find_stream(IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port) { stream_id identifier(serialize(client_addr), client_port, serialize(server_addr), server_port); streams_type::iterator iter = streams_.find(identifier); @@ -463,7 +540,7 @@ TCPStream& TCPStreamFollower::find_stream(IPv4Address client_addr, uint16_t clie } } -TCPStreamFollower::stream_id TCPStreamFollower::make_stream_id(const PDU& packet) { +StreamFollower::stream_id StreamFollower::make_stream_id(const PDU& packet) { const TCP* tcp = packet.find_pdu(); if (!tcp) { // TODO: define proper exception @@ -483,23 +560,14 @@ TCPStreamFollower::stream_id TCPStreamFollower::make_stream_id(const PDU& packet } } -TCPStream TCPStreamFollower::make_stream(const PDU& packet) { - TCPStream stream(packet); - stream.client_data_callback(on_client_data_callback_); - stream.server_data_callback(on_server_data_callback_); - stream.client_buffering_callback(on_client_buffering_callback_); - stream.server_buffering_callback(on_server_buffering_callback_); - return stream; -} - -TCPStreamFollower::address_type TCPStreamFollower::serialize(IPv4Address address) { +StreamFollower::address_type StreamFollower::serialize(IPv4Address address) { address_type addr; OutputMemoryStream output(addr.data(), addr.size()); output.write(address); return addr; } -TCPStreamFollower::address_type TCPStreamFollower::serialize(const IPv6Address& address) { +StreamFollower::address_type StreamFollower::serialize(const IPv6Address& address) { address_type addr; OutputMemoryStream output(addr.data(), addr.size()); output.write(address); @@ -508,10 +576,10 @@ TCPStreamFollower::address_type TCPStreamFollower::serialize(const IPv6Address& // stream_id -TCPStreamFollower::stream_id::stream_id(const address_type& client_addr, - uint16_t client_port, - const address_type& server_addr, - uint16_t server_port) +StreamFollower::stream_id::stream_id(const address_type& client_addr, + uint16_t client_port, + const address_type& server_addr, + uint16_t server_port) : min_address(client_addr), max_address(server_addr), min_address_port(client_port), max_address_port(server_port) { if (min_address > max_address) { @@ -520,7 +588,7 @@ max_address_port(server_port) { } } -bool TCPStreamFollower::stream_id::operator<(const stream_id& rhs) const { +bool StreamFollower::stream_id::operator<(const stream_id& rhs) const { return tie(min_address, min_address_port, max_address, max_address_port) < tie(rhs.min_address, rhs.min_address_port, rhs.max_address, rhs.max_address_port); } diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index 30cd17e..66a110e 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -21,7 +21,7 @@ using namespace std; using namespace Tins; using namespace Tins::TCPIP; -class TCPFlowTest : public testing::Test { +class FlowTest : public testing::Test { public: struct order_element { order_element(size_t payload_index, uint32_t payload_size) @@ -40,17 +40,18 @@ public: static const string payload; typedef vector ordering_info_type; - void cumulative_flow_data_handler(TCPFlow& flow); - void cumulative_stream_client_data_handler(TCPStream& stream); - void cumulative_stream_server_data_handler(TCPStream& stream); - void buffered_payload_handle(TCPFlow& session); + void cumulative_flow_data_handler(Flow& flow); + void on_new_stream(Stream& stream); + void cumulative_stream_client_data_handler(Stream& stream); + void cumulative_stream_server_data_handler(Stream& stream); + void buffered_payload_handle(Flow& session); void run_test(uint32_t initial_seq, const ordering_info_type& chunks, const string& payload); void run_test(uint32_t initial_seq, const ordering_info_type& chunks); void run_tests(const ordering_info_type& chunks, const string& payload); void run_tests(const ordering_info_type& chunks); ordering_info_type split_payload(const string& payload, uint32_t chunk_size); - string merge_chunks(const vector& chunks); + string merge_chunks(const vector& chunks); vector chunks_to_packets(uint32_t initial_seq, const ordering_info_type& chunks, const string& payload); @@ -61,77 +62,83 @@ public: uint16_t src_port, IPv4Address dst_addr, uint16_t dst_port); - vector flow_payload_chunks; - vector stream_client_payload_chunks; - vector stream_server_payload_chunks; + vector flow_payload_chunks; + vector stream_client_payload_chunks; + vector stream_server_payload_chunks; }; -const string TCPFlowTest::payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " - "Sed at aliquam arcu. Sed at iaculis magna. Nam ut dolor " - "eget velit mattis posuere ut non dui. Aliquam faucibus " - "erat pretium ligula tincidunt eget tristique justo placerat. " - "Phasellus turpis tellus, ornare ultricies egestas vitae, " - "mollis sed neque. Sed et libero in nunc pharetra auctor ut " - "a eros. Mauris quis faucibus nibh. \nLorem ipsum dolor sit " - "amet, consectetur adipiscing elit. Sed at aliquam arcu. " - "Sed at iaculis magna. Nam ut dolor eget velit mattis " - "posuere ut non dui. Aliquam faucibus erat pretium ligula " - "tincidunt eget tristique justo placerat. Phasellus turpis " - "tellus, ornare ultricies egestas vitae, mollis sed neque. " - "Sed et libero in nunc pharetra auctor ut a eros. Mauris " - "quis faucibus nibh. \n\n\nCurabitur sem erat, bibendum " - "quis condimentum ut, imperdiet at est. Duis sagittis rhoncus " - "felis at ultricies. In libero urna, dignissim eu elementum " - "quis, consectetur a neque. Praesent leo sem, cursus sed lobortis " - "sit amet, ornare ac augue. Mauris tristique semper ipsum at " - "consequat. Sed fringilla dolor ut lacus sagittis quis ultricies " - "leo vulputate. Maecenas dignissim imperdiet justo. Cras libero " - "odio, vehicula et adipiscing quis, luctus vel ante. \nAliquam " - "imperdiet est quis nunc malesuada eget convallis tellus " - "ullamcorper. Vivamus ullamcorper eros sit amet odio sollicitudin " - "rutrum. Donec pellentesque faucibus nulla, ut fringilla risus " - "aliquam eget. Sed et ante mi. Morbi a turpis et tellus dapibus " - "iaculis. Etiam faucibus tellus sed metus consequat rutrum. " - "Fusce sit amet nulla massa, tempus vulputate sem. Cras tincidunt " - "quam in libero rutrum interdum. Aliquam quam sapien, facilisis " - "at vestibulum et, venenatis id mauris. Morbi rutrum gravida " - "ultricies. \nAenean et justo ut libero euismod sollicitudin. " - "Nullam enim dui, iaculis vitae bibendum et, commodo in tellus. " - "Nullam eget purus mi, a ullamcorper lorem. Suspendisse potenti. " - "Duis ac justo ut leo euismod gravida sit amet at lectus. Lorem " - "ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sed " - "arcu vitae nisi sollicitudin gravida. Nulla facilisis nibh turpis. " - "Maecenas quis imperdiet arcu. Sed sit amet nulla urna, at " - "vestibulum mauris. Suspendisse quis elit dui. Class aptent taciti " - "sociosqu ad litora torquent per conubia nostra, per inceptos " - "himenaeos. \n"; +const string FlowTest::payload = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Sed at aliquam arcu. Sed at iaculis magna. Nam ut dolor " + "eget velit mattis posuere ut non dui. Aliquam faucibus " + "erat pretium ligula tincidunt eget tristique justo placerat. " + "Phasellus turpis tellus, ornare ultricies egestas vitae, " + "mollis sed neque. Sed et libero in nunc pharetra auctor ut " + "a eros. Mauris quis faucibus nibh. \nLorem ipsum dolor sit " + "amet, consectetur adipiscing elit. Sed at aliquam arcu. " + "Sed at iaculis magna. Nam ut dolor eget velit mattis " + "posuere ut non dui. Aliquam faucibus erat pretium ligula " + "tincidunt eget tristique justo placerat. Phasellus turpis " + "tellus, ornare ultricies egestas vitae, mollis sed neque. " + "Sed et libero in nunc pharetra auctor ut a eros. Mauris " + "quis faucibus nibh. \n\n\nCurabitur sem erat, bibendum " + "quis condimentum ut, imperdiet at est. Duis sagittis rhoncus " + "felis at ultricies. In libero urna, dignissim eu elementum " + "quis, consectetur a neque. Praesent leo sem, cursus sed lobortis " + "sit amet, ornare ac augue. Mauris tristique semper ipsum at " + "consequat. Sed fringilla dolor ut lacus sagittis quis ultricies " + "leo vulputate. Maecenas dignissim imperdiet justo. Cras libero " + "odio, vehicula et adipiscing quis, luctus vel ante. \nAliquam " + "imperdiet est quis nunc malesuada eget convallis tellus " + "ullamcorper. Vivamus ullamcorper eros sit amet odio sollicitudin " + "rutrum. Donec pellentesque faucibus nulla, ut fringilla risus " + "aliquam eget. Sed et ante mi. Morbi a turpis et tellus dapibus " + "iaculis. Etiam faucibus tellus sed metus consequat rutrum. " + "Fusce sit amet nulla massa, tempus vulputate sem. Cras tincidunt " + "quam in libero rutrum interdum. Aliquam quam sapien, facilisis " + "at vestibulum et, venenatis id mauris. Morbi rutrum gravida " + "ultricies. \nAenean et justo ut libero euismod sollicitudin. " + "Nullam enim dui, iaculis vitae bibendum et, commodo in tellus. " + "Nullam eget purus mi, a ullamcorper lorem. Suspendisse potenti. " + "Duis ac justo ut leo euismod gravida sit amet at lectus. Lorem " + "ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sed " + "arcu vitae nisi sollicitudin gravida. Nulla facilisis nibh turpis. " + "Maecenas quis imperdiet arcu. Sed sit amet nulla urna, at " + "vestibulum mauris. Suspendisse quis elit dui. Class aptent taciti " + "sociosqu ad litora torquent per conubia nostra, per inceptos " + "himenaeos. \n"; -void TCPFlowTest::cumulative_flow_data_handler(TCPFlow& flow) { +void FlowTest::cumulative_flow_data_handler(Flow& flow) { flow_payload_chunks.push_back(flow.payload()); flow.payload().clear(); } -void TCPFlowTest::cumulative_stream_client_data_handler(TCPStream& stream) { +void FlowTest::on_new_stream(Stream& stream) { + using std::placeholders::_1; + stream.client_data_callback(bind(&FlowTest::cumulative_stream_client_data_handler, + this, _1)); +} + +void FlowTest::cumulative_stream_client_data_handler(Stream& stream) { stream_client_payload_chunks.push_back(stream.client_flow().payload()); stream.client_flow().payload().clear(); } -void TCPFlowTest::cumulative_stream_server_data_handler(TCPStream& stream) { +void FlowTest::cumulative_stream_server_data_handler(Stream& stream) { stream_server_payload_chunks.push_back(stream.server_flow().payload()); stream.server_flow().payload().clear(); } -void TCPFlowTest::buffered_payload_handle(TCPFlow& session) { +void FlowTest::buffered_payload_handle(Flow& session) { } -void TCPFlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks, - const string& payload) { +void FlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks, + const string& payload) { using std::placeholders::_1; flow_payload_chunks.clear(); - TCPFlow flow(IPv4Address("1.2.3.4"), 22, initial_seq); - flow.data_callback(bind(&TCPFlowTest::cumulative_flow_data_handler, this, _1)); + Flow flow(IPv4Address("1.2.3.4"), 22, initial_seq); + flow.data_callback(bind(&FlowTest::cumulative_flow_data_handler, this, _1)); vector packets = chunks_to_packets(initial_seq, chunks, payload); for (size_t i = 0; i < packets.size(); ++i) { flow.process_packet(packets[i]); @@ -140,11 +147,11 @@ void TCPFlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunk EXPECT_EQ(payload, string(flow_payload.begin(), flow_payload.end())); } -void TCPFlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks) { +void FlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks) { run_test(initial_seq, chunks, payload); } -void TCPFlowTest::run_tests(const ordering_info_type& chunks, const string& payload) { +void FlowTest::run_tests(const ordering_info_type& chunks, const string& payload) { run_test(0, chunks, payload); run_test(20, chunks, payload); run_test(numeric_limits::max() / 2, chunks, payload); @@ -155,12 +162,12 @@ void TCPFlowTest::run_tests(const ordering_info_type& chunks, const string& payl run_test(numeric_limits::max() - 31, chunks, payload); } -void TCPFlowTest::run_tests(const ordering_info_type& chunks) { +void FlowTest::run_tests(const ordering_info_type& chunks) { run_tests(chunks, payload); } -TCPFlowTest::ordering_info_type TCPFlowTest::split_payload(const string& payload, - uint32_t chunk_size) { +FlowTest::ordering_info_type FlowTest::split_payload(const string& payload, + uint32_t chunk_size) { ordering_info_type output; uint32_t chunk_count = payload.size() / chunk_size; for (uint32_t i = 0; i < chunk_count; ++i) { @@ -173,18 +180,18 @@ TCPFlowTest::ordering_info_type TCPFlowTest::split_payload(const string& payload return output; } -string TCPFlowTest::merge_chunks(const vector& chunks) { +string FlowTest::merge_chunks(const vector& chunks) { string output; for (size_t i = 0; i < chunks.size(); ++i) { - TCPFlow::payload_type this_chunk = chunks[i]; + Flow::payload_type this_chunk = chunks[i]; output += string(this_chunk.begin(), this_chunk.end()); } return output; } -vector TCPFlowTest::chunks_to_packets(uint32_t initial_seq, - const ordering_info_type& chunks, - const string& payload) { +vector FlowTest::chunks_to_packets(uint32_t initial_seq, + const ordering_info_type& chunks, + const string& payload) { vector output; for (size_t i = 0; i < chunks.size(); ++i) { const order_element& element = chunks[i]; @@ -198,9 +205,9 @@ vector TCPFlowTest::chunks_to_packets(uint32_t initial_seq, return output; } -vector TCPFlowTest::three_way_handshake(uint32_t client_seq, uint32_t server_seq, - IPv4Address client_addr, uint16_t client_port, - IPv4Address server_addr, uint16_t server_port) { +vector FlowTest::three_way_handshake(uint32_t client_seq, uint32_t server_seq, + IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port) { vector output; output.push_back(EthernetII() / IP(server_addr, client_addr) / TCP(server_port, client_port)); output.push_back(EthernetII() / IP(client_addr, server_addr) / TCP(client_port, server_port)); @@ -216,9 +223,9 @@ vector TCPFlowTest::three_way_handshake(uint32_t client_seq, uint32_ return output; } -void TCPFlowTest::set_endpoints(vector& packets, IPv4Address src_addr, - uint16_t src_port, IPv4Address dst_addr, - uint16_t dst_port) { +void FlowTest::set_endpoints(vector& packets, IPv4Address src_addr, + uint16_t src_port, IPv4Address dst_addr, + uint16_t dst_port) { for (size_t i = 0; i < packets.size(); ++i) { packets[i].rfind_pdu().src_addr(src_addr); packets[i].rfind_pdu().dst_addr(dst_addr); @@ -227,12 +234,12 @@ void TCPFlowTest::set_endpoints(vector& packets, IPv4Address src_add } } -TEST_F(TCPFlowTest, ReassembleStreamPlain) { +TEST_F(FlowTest, ReassembleStreamPlain) { ordering_info_type chunks = split_payload(payload, 5); run_tests(chunks); } -TEST_F(TCPFlowTest, ReassembleStreamReordering) { +TEST_F(FlowTest, ReassembleStreamReordering) { ordering_info_type chunks = split_payload(payload, 5); // e.g. input [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // after this it's [2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8] @@ -244,13 +251,13 @@ TEST_F(TCPFlowTest, ReassembleStreamReordering) { run_tests(chunks); } -TEST_F(TCPFlowTest, ReassembleStreamReversed) { +TEST_F(FlowTest, ReassembleStreamReversed) { ordering_info_type chunks = split_payload(payload, 5); reverse(chunks.begin(), chunks.end()); run_tests(chunks); } -TEST_F(TCPFlowTest, Overlapping) { +TEST_F(FlowTest, Overlapping) { string payload = "Hello world. This is a payload"; ordering_info_type chunks; // "Hello " @@ -270,35 +277,100 @@ TEST_F(TCPFlowTest, Overlapping) { run_tests(chunks, payload); } -TEST_F(TCPFlowTest, TCPStreamFollower_ThreeWayHandshake) { +TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { using std::placeholders::_1; vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); - TCPStreamFollower follower; - follower.client_data_callback(bind(&TCPFlowTest::cumulative_stream_client_data_handler, - this, _1)); + StreamFollower follower; + follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); for (size_t i = 0; i < packets.size(); ++i) { follower.process_packet(packets[i]); } - TCPStream& stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); - EXPECT_EQ(TCPFlow::ESTABLISHED, stream.client_flow().state()); - EXPECT_EQ(TCPFlow::SYN_SENT, stream.server_flow().state()); + Stream& stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); + EXPECT_EQ(Flow::ESTABLISHED, stream.client_flow().state()); + EXPECT_EQ(Flow::SYN_SENT, stream.server_flow().state()); EXPECT_EQ(30, stream.client_flow().sequence_number()); EXPECT_EQ(60, stream.server_flow().sequence_number()); EXPECT_EQ(IPv4Address("4.3.2.1"), stream.client_flow().dst_addr_v4()); EXPECT_EQ(25, stream.client_flow().dport()); EXPECT_EQ(IPv4Address("1.2.3.4"), stream.server_flow().dst_addr_v4()); EXPECT_EQ(22, stream.server_flow().dport()); + EXPECT_EQ(IPv4Address("1.2.3.4"), stream.client_addr_v4()); + EXPECT_EQ(IPv4Address("4.3.2.1"), stream.server_addr_v4()); + EXPECT_EQ(22, stream.client_port()); + EXPECT_EQ(25, stream.server_port()); IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); server_packet.rfind_pdu().flags(TCP::ACK); follower.process_packet(server_packet); - EXPECT_EQ(TCPFlow::ESTABLISHED, stream.server_flow().state()); + EXPECT_EQ(Flow::ESTABLISHED, stream.server_flow().state()); EXPECT_EQ(61, stream.server_flow().sequence_number()); } -TEST_F(TCPFlowTest, TCPStreamFollower_FollowStream) { +TEST_F(FlowTest, StreamFollower_RSTClosesStream) { + using std::placeholders::_1; + + vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + StreamFollower follower; + follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); + for (size_t i = 0; i < packets.size(); ++i) { + follower.process_packet(packets[i]); + } + Stream stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); + + IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); + server_packet.rfind_pdu().flags(TCP::RST); + stream.process_packet(server_packet); + + EXPECT_EQ(Flow::RST_SENT, stream.server_flow().state()); + EXPECT_TRUE(stream.is_finished()); +} + +TEST_F(FlowTest, StreamFollower_FINClosesStream) { + using std::placeholders::_1; + + vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + StreamFollower follower; + follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); + for (size_t i = 0; i < packets.size(); ++i) { + follower.process_packet(packets[i]); + } + Stream stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); + + IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); + server_packet.rfind_pdu().flags(TCP::FIN | TCP::ACK); + stream.process_packet(server_packet); + + EXPECT_EQ(Flow::FIN_SENT, stream.server_flow().state()); + EXPECT_FALSE(stream.is_finished()); + + IP client_packet = IP("4.3.2.1", "1.2.3.4") / TCP(25, 22); + client_packet.rfind_pdu().flags(TCP::FIN | TCP::ACK); + stream.process_packet(client_packet); + + EXPECT_EQ(Flow::FIN_SENT, stream.client_flow().state()); + EXPECT_TRUE(stream.is_finished()); +} + +TEST_F(FlowTest, StreamFollower_StreamIsRemovedWhenFinished) { + using std::placeholders::_1; + + vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + StreamFollower follower; + follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); + for (size_t i = 0; i < packets.size(); ++i) { + follower.process_packet(packets[i]); + } + IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); + server_packet.rfind_pdu().flags(TCP::RST); + follower.process_packet(server_packet); + + // We shouldn't be able to find it + EXPECT_THROW(follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25), runtime_error); +} + +TEST_F(FlowTest, StreamFollower_FollowStream) { using std::placeholders::_1; vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); @@ -306,9 +378,8 @@ TEST_F(TCPFlowTest, TCPStreamFollower_FollowStream) { vector chunk_packets = chunks_to_packets(30 /*initial_seq*/, chunks, payload); set_endpoints(chunk_packets, "1.2.3.4", 22, "4.3.2.1", 25); packets.insert(packets.end(), chunk_packets.begin(), chunk_packets.end()); - TCPStreamFollower follower; - follower.client_data_callback(bind(&TCPFlowTest::cumulative_stream_client_data_handler, - this, _1)); + StreamFollower follower; + follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); for (size_t i = 0; i < packets.size(); ++i) { follower.process_packet(packets[i]); } From 5b60b79fd88a2cfa5407d8257fcc67cdb8160506 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sun, 7 Feb 2016 19:24:46 -0800 Subject: [PATCH 03/19] Document new TCP stream classes --- cmake/appveyor.yml | 2 +- examples/http_dump.cpp | 5 - include/tins/exceptions.h | 10 + include/tins/tcp_ip.h | 401 ++++++++++++++++++++++++++++++++++++-- src/tcp_ip.cpp | 64 +++--- tests/src/tcp_ip.cpp | 17 +- 6 files changed, 444 insertions(+), 55 deletions(-) diff --git a/cmake/appveyor.yml b/cmake/appveyor.yml index cc7c560..3b69ee6 100644 --- a/cmake/appveyor.yml +++ b/cmake/appveyor.yml @@ -37,7 +37,7 @@ after_build: - 7z a libtins-%platform%-%Configuration%.zip libtins test_script: - cd c:\projects\libtins\build -- ctest -C %Configuration% +- ctest -C %Configuration% -V deploy_script: - ps: Push-AppveyorArtifact "install\libtins-$env:Platform-$env:Configuration.zip" skip_commits: diff --git a/examples/http_dump.cpp b/examples/http_dump.cpp index dcd2c8f..5daad0a 100644 --- a/examples/http_dump.cpp +++ b/examples/http_dump.cpp @@ -89,10 +89,6 @@ void on_client_data(Stream& stream) { // Now print it, prepending some information about the stream cout << client_endpoint(stream) << " >> " << server_endpoint(stream) << ": " << endl << data << endl; - // Now erase the stored data, as we've already processed it. This is important, - // since if we don't do this, the connection will keep buffering data until - // the stream is closed - stream.client_payload().clear(); } // Whenever there's new server data on the stream, this callback is executed. @@ -101,7 +97,6 @@ void on_server_data(Stream& stream) { string data(stream.server_payload().begin(), stream.server_payload().end()); cout << server_endpoint(stream) << " >> " << client_endpoint(stream) << ": " << endl << data << endl; - stream.server_payload().clear(); } // When a connection is closed, this callback is executed. diff --git a/include/tins/exceptions.h b/include/tins/exceptions.h index 436a67f..1b7580c 100644 --- a/include/tins/exceptions.h +++ b/include/tins/exceptions.h @@ -273,6 +273,16 @@ public: } }; +/** + * \brief Exception thrown when a stream is not found + */ +class stream_not_found : public exception_base { +public: + const char* what() const throw() { + return "Stream not found"; + } +}; + namespace Crypto { namespace WPA2 { /** diff --git a/include/tins/tcp_ip.h b/include/tins/tcp_ip.h index 2d3b110..bf69f18 100644 --- a/include/tins/tcp_ip.h +++ b/include/tins/tcp_ip.h @@ -51,8 +51,29 @@ class IPv6Address; namespace TCPIP { +/** + * \brief Represents an unidirectional TCP flow between 2 endpoints + * + * This class will keep the state for all the traffic sent by + * one of the peers in a TCP connection. This contains the sequence number, + * payload ready to be read and buffered payload, along with some other + * properties of the flow. + * + * A TCP stream (see class Stream) is made out of 2 Flows, so you should + * probably have a look at that class first. + * + * You shouldn't normally need to interact with this class. Stream already + * provides proxys to most of its Flow's attributes. + */ class TINS_API Flow { public: + /** + * \brief Enum that indicates the state of this flow. + * + * Note that although similar, this is not mapped to a TCP state-machine + * state. This is mostly used internally to know which packets the flow is + * expecting and to know when it's done sending data. + */ enum State { UNKNOWN, SYN_SENT, @@ -61,35 +82,154 @@ public: RST_SENT }; + /** + * 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; + + /** + * The type used to store the callbacks that this class triggers + */ typedef std::function event_callback; - Flow(const IPv4Address& dest_address, uint16_t dest_port, - uint32_t sequence_number); - - Flow(const IPv6Address& dest_address, uint16_t dest_port, + /** + * Construct a Flow from an IPv4 address + * + * \param dst_address This flow's destination address + * \param dst_port This flow's destination port + * \param sequence_number The initial sequence number to be used + */ + Flow(const IPv4Address& dst_address, uint16_t dst_port, + uint32_t sequence_number); + + /** + * Construct a Flow from an IPv6 address + * + * \param dst_address This flow's destination address + * \param dst_port This flow's destination port + * \param sequence_number The initial sequence number to be used + */ + Flow(const IPv6Address& dst_address, uint16_t dst_port, uint32_t sequence_number); + /** + * \brief Sets the callback that will be executed when data is readable + * + * Whenever this flow has readable data, this callback will be executed. + * By readable, this means that there's non-out-of-order data captured. + * + * \param callback The callback to be executed + */ void data_callback(const event_callback& callback); + + /** + * \brief Sets the callback that will be executed when data is buffered. + * + * Whenever this flow receives out-of-order data, this callback will be + * executed. + * + * \param callback The callback to be executed + */ void buffering_callback(const event_callback& callback); + /** + * \brief Processes a packet. + * + * If this packet contains data and starts or overlaps with the current + * sequence number, then the data will be appended to this flow's payload + * and the data_callback will be executed. + * + * If this packet contains out-of-order data, it will be buffered and the + * buffering_callback will be executed. + * + * \param pdu The packet to be processed + * \sa Flow::data_callback + * \sa Flow::buffering_callback + */ void process_packet(PDU& pdu); + /** + * Indicates whether this flow uses IPv6 addresses + */ bool is_v6() const; + + /** + * \brief Indicates whether this flow is finished + * + * A finished is considered to be finished if either it sent a + * packet with the FIN or RST flags on. + */ bool is_finished() const; + + /** + * \brief Indicates whether a packet belongs to this flow + * + * Since Flow represents a unidirectional stream, this will only check + * the destination endpoint and not the source one. + * + * \param packet The packet to be checked + */ bool packet_belongs(const PDU& packet) const; + /** + * \brief Getter for the IPv4 destination address + * + * Note that it's only safe to execute this method if is_v6() == false + */ IPv4Address dst_addr_v4() const; + + /** + * \brief Getter for the IPv6 destination address + * + * Note that it's only safe to execute this method if is_v6() == true + */ IPv6Address dst_addr_v6() const; + + /** + * Getter for this flow's destination port + */ uint16_t dport() const; + + /** + * Getter for this flow's payload (const) + */ const payload_type& payload() const; + + /** + * Getter for this flow's destination port + */ payload_type& payload(); + + /** + * Getter for this flow's state + */ State state() const; + + /** + * Getter for this flow's sequence number + */ uint32_t sequence_number() const; + + /** + * Getter for this flow's buffered payload (const) + */ const buffered_payload_type& buffered_payload() const; + + /** + * Getter for this flow's buffered payload + */ buffered_payload_type& buffered_payload(); + /** + * Sets the state of this flow + * + * \param new_state The new state of this flow + */ void state(State new_state); private: void store_payload(uint32_t seq, const payload_type& payload); @@ -103,57 +243,208 @@ private: uint16_t dest_port_; event_callback on_data_callback_; event_callback on_buffering_callback_; - bool is_v6_; State state_; + bool is_v6_; }; +/** + * \brief Represents a TCP stream + * + * A TCP stream is made out of 2 Flows, one in each direction, plus + * some other attributes and callbacks. + * + * This class works using callbacks. Whenever the stream is created, you should + * set at least the client/server callbacks so you are notified whenever the + * client/server has sent data. Note that setting these is not mandatory, so + * you can subscribe to just the callbacks you need. + * + * \sa Stream::auto_cleanup_payloads + */ class TINS_API Stream { public: - enum State { - SYN_SENT, - SYN_RCVD, - ESTABLISHED, - CLOSE_WAIT, - FIN_WAIT_1, - FIN_WAIT_2, - TIME_WAIT, - CLOSED - }; - + /** + * The type used for callbacks + */ typedef std::function stream_callback; + + /** + * The type used to store payloads + */ typedef Flow::payload_type payload_type; + /** + * \brief Constructs a TCP stream using the provided packet. + */ Stream(const PDU& initial_packet); - Stream(const Flow& client_flow, const Flow& server_flow); + /** + * \brief Processes this packet. + * + * This will forward the packet appropriately to the client + * or server flow. + */ void process_packet(PDU& packet); + /** + * Getter for the client flow + */ Flow& client_flow(); + + /** + * Getter for the client flow (const) + */ const Flow& client_flow() const; + + /** + * Getter for the server flow + */ Flow& server_flow(); + + /** + * Getter for the server flow (const) + */ const Flow& server_flow() const; + /** + * \brief Indicates whether this stream is finished. + * + * This stream is finished if either peer sent a packet with + * the RST flag on, or both peers sent a FIN. + */ bool is_finished() const; + + /** + * Indicates whether this packet uses IPv6 addresses + */ bool is_v6() const; + /** + * \brief Retrieves the client's IPv4 address + * + * Note that it's only valid to call this method if is_v6() == false + */ IPv4Address client_addr_v4() const; + + /** + * \brief Retrieves the client's IPv6 address + * + * Note that it's only valid to call this method if is_v6() == true + */ IPv6Address client_addr_v6() const; + + /** + * \brief Retrieves the server's IPv4 address + * + * Note that it's only valid to call this method if is_v6() == false + */ IPv4Address server_addr_v4() const; + + /** + * \brief Retrieves the server's IPv6 address + * + * Note that it's only valid to call this method if is_v6() == true + */ IPv6Address server_addr_v6() const; + + /** + * Getter for the client's port + */ uint16_t client_port() const; + + /** + * Getter for the server's port + */ uint16_t server_port() const; + + /** + * Getter for the client's payload (const) + */ const payload_type& client_payload() const; + + /** + * Getter for the client's payload + */ payload_type& client_payload(); + + /** + * Getter for the server's payload (const) + */ const payload_type& server_payload() const; + + /** + * Getter for the server's payload + */ payload_type& server_payload(); + /** + * \brief Sets the callback to be executed when the stream is closed + * + * \param callback The callback to be set + */ void stream_closed_callback(const stream_callback& callback); + + /** + * \brief Sets the callback to be executed when there's client data + * + * \sa Flow::data_callback + * \param callback The callback to be set + */ void client_data_callback(const stream_callback& callback); + + /** + * \brief Sets the callback to be executed when there's server data + * + * \sa Flow::data_callback + * \param callback The callback to be set + */ void server_data_callback(const stream_callback& callback); + + /** + * \brief Sets the callback to be executed when there's new buffered + * client data + * + * \sa Flow::buffering_callback + * \param callback The callback to be set + */ void client_buffering_callback(const stream_callback& callback); + + /** + * \brief Sets the callback to be executed when there's new buffered + * client data + * + * \sa Flow::buffering_callback + * \param callback The callback to be set + */ void server_buffering_callback(const stream_callback& callback); + /** + * \brief Sets the internal callbacks. + * + * This shouldn't normally need to be called except if you're constructing + * this object and then moving it around before persisting it somewhere. + */ void setup_flows_callbacks(); + + /** + * \brief Indicates whether each flow's payloads should be automatically + * erased. + * + * If this property is true, then whenever there's new data for a stream, + * the appropriate callback will be executed and then the payload will be + * erased. + * + * If this property is false, then the payload will not be erased + * and the user is responsible for clearing the payload vector. + * + * Setting this property to false is useful if it's desired to hold all + * of the data sent on the stream before processing it. Note that this + * can lead to the memory growing a lot. + * + * This property is true by default. + * + * \param value The value to be set for this property + */ + void auto_cleanup_payloads(bool value); private: static Flow extract_client_flow(const PDU& packet); static Flow extract_server_flow(const PDU& packet); @@ -170,20 +461,91 @@ private: stream_callback on_server_data_callback_; stream_callback on_client_buffering_callback_; stream_callback on_server_buffering_callback_; - State state_; + bool auto_cleanup_; }; +/** + * \brief Represents a class that follows TCP and reassembles streams + * + * This class processes packets and whenever it detects a new connection + * being open, it starts tracking it. This will follow all data sent by + * each peer and make it available to the user in a simple way. + * + * In order to use this class, just create an instance and set the + * new stream callback to some function that you want: + * + * \code + * void on_new_stream(TCPStream& stream) { + * // Do something with it. + * // This is the perfect time to set the stream's client/server + * // write callbacks so you are notified whenever there's new + * // data on the stream + * } + * + * // Create it + * StreamFollower follower; + * // Set the callback + * follower.new_stream_callback(&on_new_stream); + * \endcode + */ class TINS_API StreamFollower { public: + /** + * \brief The type used for callbacks + */ typedef Stream::stream_callback stream_callback; + /** + * Default constructor + */ StreamFollower(); + /** + * \brief Processes a packet + * + * This will detect if this packet belongs to an existing stream + * and process it, or if it belongs to a new one, in which case it + * starts tracking it. + * + * This method always returns true so it can be easily plugged as + * the argument to Sniffer::sniff_loop. + * + * \param packet The packet to be processed + * \return Always true + */ bool process_packet(PDU& packet); + + /** + * \brief Sets the callback to be executed when a new stream is captured. + * + * Whenever a new stream is captured, the provided callback will be + * executed. + * + * \param callback The callback to be set + */ void new_stream_callback(const stream_callback& callback); + /** + * Finds the stream identified by the provided arguments. + * + * \param client_addr The client's address + * \param client_port The client's port + * \param server_addr The server's address + * \param server_addr The server's port + */ Stream& find_stream(IPv4Address client_addr, uint16_t client_port, - IPv4Address server_addr, uint16_t server_port); + IPv4Address server_addr, uint16_t server_port); + + /** + * Finds the stream identified by the provided arguments. + * + * \param client_addr The client's address + * \param client_port The client's port + * \param server_addr The server's address + * \param server_addr The server's port + */ + Stream& find_stream(IPv6Address client_addr, uint16_t client_port, + IPv6Address server_addr, uint16_t server_port); private: static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; typedef std::array address_type; @@ -205,6 +567,7 @@ private: typedef std::map streams_type; stream_id make_stream_id(const PDU& packet); + Stream& find_stream(const stream_id& id); static address_type serialize(IPv4Address address); static address_type serialize(const IPv6Address& address); diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp index 8e90f36..ee59244 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip.cpp @@ -72,16 +72,16 @@ int seq_compare(uint32_t seq1, uint32_t seq2) { Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), is_v6_(false), - state_(UNKNOWN) { +: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), + is_v6_(false) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); } Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), is_v6_(true), - state_(UNKNOWN) { +: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), + is_v6_(true) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); } @@ -282,14 +282,13 @@ void Flow::state(State new_state) { // Stream Stream::Stream(const PDU& packet) -: client_flow_(extract_client_flow(packet)), -server_flow_(extract_server_flow(packet)) { - -} - -Stream::Stream(const Flow& client_flow, const Flow& server_flow) -: client_flow_(client_flow), server_flow_(server_flow) { - +: client_flow_(extract_client_flow(packet)), + server_flow_(extract_server_flow(packet)), auto_cleanup_(true) { + const TCP& tcp = packet.rfind_pdu(); + // If it's a SYN, set the proper state + if (tcp.flags() == TCP::SYN) { + client_flow().state(Flow::SYN_SENT); + } } void Stream::process_packet(PDU& packet) { @@ -441,17 +440,26 @@ void Stream::setup_flows_callbacks() { server_flow_.buffering_callback(bind(&Stream::on_server_buffering, this, _1)); } +void Stream::auto_cleanup_payloads(bool value) { + auto_cleanup_ = value; +} -void Stream::on_client_flow_data(const Flow& flow) { +void Stream::on_client_flow_data(const Flow& /*flow*/) { if (on_client_data_callback_) { on_client_data_callback_(*this); } + if (auto_cleanup_) { + client_payload().clear(); + } } -void Stream::on_server_flow_data(const Flow& flow) { +void Stream::on_server_flow_data(const Flow& /*flow*/) { if (on_server_data_callback_) { on_server_data_callback_(*this); } + if (auto_cleanup_) { + server_payload().clear(); + } } void Stream::on_client_buffering(const Flow& flow) { @@ -494,8 +502,6 @@ bool StreamFollower::process_packet(PDU& packet) { throw runtime_error("No new connection callback set"); } if (tcp.flags() == TCP::SYN) { - // If it's a SYN, set the proper state - iter->second.client_flow().state(Flow::SYN_SENT); process = false; } else { @@ -530,14 +536,14 @@ Stream& StreamFollower::find_stream(IPv4Address client_addr, uint16_t client_por IPv4Address server_addr, uint16_t server_port) { stream_id identifier(serialize(client_addr), client_port, serialize(server_addr), server_port); - streams_type::iterator iter = streams_.find(identifier); - if (iter == streams_.end()) { - // TODO: define proper exception - throw runtime_error("Stream not found"); - } - else { - return iter->second; - } + return find_stream(identifier); +} + +Stream& StreamFollower::find_stream(IPv6Address client_addr, uint16_t client_port, + IPv6Address server_addr, uint16_t server_port) { + stream_id identifier(serialize(client_addr), client_port, + serialize(server_addr), server_port); + return find_stream(identifier); } StreamFollower::stream_id StreamFollower::make_stream_id(const PDU& packet) { @@ -560,6 +566,16 @@ StreamFollower::stream_id StreamFollower::make_stream_id(const PDU& packet) { } } +Stream& StreamFollower::find_stream(const stream_id& id) { + streams_type::iterator iter = streams_.find(id); + if (iter == streams_.end()) { + throw stream_not_found(); + } + else { + return iter->second; + } +} + StreamFollower::address_type StreamFollower::serialize(IPv4Address address) { address_type addr; OutputMemoryStream output(addr.data(), addr.size()); diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index 66a110e..d4f2bd7 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -13,6 +13,7 @@ #include "ip.h" #include "ip_address.h" #include "ipv6_address.h" +#include "exceptions.h" #include "ethernetII.h" #include "rawpdu.h" #include "utils.h" @@ -120,12 +121,10 @@ void FlowTest::on_new_stream(Stream& stream) { void FlowTest::cumulative_stream_client_data_handler(Stream& stream) { stream_client_payload_chunks.push_back(stream.client_flow().payload()); - stream.client_flow().payload().clear(); } void FlowTest::cumulative_stream_server_data_handler(Stream& stream) { stream_server_payload_chunks.push_back(stream.server_flow().payload()); - stream.server_flow().payload().clear(); } void FlowTest::buffered_payload_handle(Flow& session) { @@ -286,7 +285,8 @@ TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { for (size_t i = 0; i < packets.size(); ++i) { follower.process_packet(packets[i]); } - Stream& stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); + Stream& stream = follower.find_stream(IPv4Address("1.2.3.4"), 22, + IPv4Address("4.3.2.1"), 25); EXPECT_EQ(Flow::ESTABLISHED, stream.client_flow().state()); EXPECT_EQ(Flow::SYN_SENT, stream.server_flow().state()); EXPECT_EQ(30, stream.client_flow().sequence_number()); @@ -317,7 +317,8 @@ TEST_F(FlowTest, StreamFollower_RSTClosesStream) { for (size_t i = 0; i < packets.size(); ++i) { follower.process_packet(packets[i]); } - Stream stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); + Stream stream = follower.find_stream(IPv4Address("1.2.3.4"), 22, + IPv4Address("4.3.2.1"), 25); IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); server_packet.rfind_pdu().flags(TCP::RST); @@ -336,7 +337,8 @@ TEST_F(FlowTest, StreamFollower_FINClosesStream) { for (size_t i = 0; i < packets.size(); ++i) { follower.process_packet(packets[i]); } - Stream stream = follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25); + Stream stream = follower.find_stream(IPv4Address("1.2.3.4"), 22, + IPv4Address("4.3.2.1"), 25); IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); server_packet.rfind_pdu().flags(TCP::FIN | TCP::ACK); @@ -367,7 +369,10 @@ TEST_F(FlowTest, StreamFollower_StreamIsRemovedWhenFinished) { follower.process_packet(server_packet); // We shouldn't be able to find it - EXPECT_THROW(follower.find_stream("1.2.3.4", 22, "4.3.2.1", 25), runtime_error); + EXPECT_THROW( + follower.find_stream(IPv4Address("1.2.3.4"), 22, IPv4Address("4.3.2.1"), 25), + stream_not_found + ); } TEST_F(FlowTest, StreamFollower_FollowStream) { From 7c1453662fb6c4a44cbe154b0f9d7352a5b69c88 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sun, 7 Feb 2016 19:43:31 -0800 Subject: [PATCH 04/19] Fix compilation issues --- src/tcp_ip.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp index ee59244..c2577d2 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip.cpp @@ -27,9 +27,12 @@ * */ +#include "tcp_ip.h" + +#if TINS_IS_CXX11 + #include #include -#include "tcp_ip.h" #include "memory.h" #include "ip_address.h" #include "ipv6_address.h" @@ -41,6 +44,7 @@ #include "memory_helpers.h" using std::make_pair; +using std::bind; using std::runtime_error; using std::numeric_limits; using std::max; @@ -611,3 +615,5 @@ bool StreamFollower::stream_id::operator<(const stream_id& rhs) const { } // TCPIP } // Tins + +#endif // TINS_IS_CXX11 \ No newline at end of file From c3861cf54ec18cde8c5b14fb009258de65ca6bb8 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sun, 7 Feb 2016 19:56:41 -0800 Subject: [PATCH 05/19] Fill address arrays with 0x00 --- src/tcp_ip.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp index c2577d2..6df9149 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip.cpp @@ -583,6 +583,7 @@ Stream& StreamFollower::find_stream(const stream_id& id) { StreamFollower::address_type StreamFollower::serialize(IPv4Address address) { address_type addr; OutputMemoryStream output(addr.data(), addr.size()); + addr.fill(0); output.write(address); return addr; } @@ -590,6 +591,7 @@ StreamFollower::address_type StreamFollower::serialize(IPv4Address address) { StreamFollower::address_type StreamFollower::serialize(const IPv6Address& address) { address_type addr; OutputMemoryStream output(addr.data(), addr.size()); + addr.fill(0); output.write(address); return addr; } From 549c0e97d04860b75de8e15c37607e8303c09b80 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Mon, 8 Feb 2016 20:47:27 -0800 Subject: [PATCH 06/19] Add Flow::ignore_data_packets --- include/tins/tcp_ip.h | 27 ++++++++++++++++++++++++++- src/tcp_ip.cpp | 30 +++++++++++++++++++++++------- tests/src/tcp_ip.cpp | 16 ++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/include/tins/tcp_ip.h b/include/tins/tcp_ip.h index bf69f18..0a4e26d 100644 --- a/include/tins/tcp_ip.h +++ b/include/tins/tcp_ip.h @@ -231,8 +231,16 @@ public: * \param new_state The new state of this flow */ void state(State new_state); + + /** + * \brief Sets whether this flow should ignore data packets + * + * If the data packets are ignored then the flow will just be + * followed to keep track of its state. + */ + void ignore_data_packets(); private: - void store_payload(uint32_t seq, const payload_type& payload); + 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); @@ -245,6 +253,7 @@ private: event_callback on_buffering_callback_; State state_; bool is_v6_; + bool ignore_data_packets_; }; /** @@ -417,6 +426,22 @@ public: */ void server_buffering_callback(const stream_callback& callback); + /** + * \brief Indicates that the data packets sent by the client should be + * ignored + * + * \sa Flow::ignore_data_packets + */ + void ignore_client_data(); + + /** + * \brief Indicates that the data packets sent by the server should be + * ignored + * + * \sa Flow::ignore_data_packets + */ + void ignore_server_data(); + /** * \brief Sets the internal callbacks. * diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp index 6df9149..9a6e490 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip.cpp @@ -45,6 +45,7 @@ using std::make_pair; using std::bind; +using std::pair; using std::runtime_error; using std::numeric_limits; using std::max; @@ -77,7 +78,7 @@ int seq_compare(uint32_t seq1, uint32_t seq2) { Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, uint32_t sequence_number) : seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), - is_v6_(false) { + is_v6_(false), ignore_data_packets_(false) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); } @@ -85,7 +86,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), state_(UNKNOWN), - is_v6_(true) { + is_v6_(true), ignore_data_packets_(false) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); } @@ -105,6 +106,9 @@ void Flow::process_packet(PDU& pdu) { if (tcp) { update_state(*tcp); } + if (ignore_data_packets_) { + return; + } if (!tcp || !raw) { return; } @@ -123,7 +127,7 @@ void Flow::process_packet(PDU& pdu) { seq = seq_number_; } // Store this payload - store_payload(seq, raw->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) { @@ -139,7 +143,7 @@ void Flow::process_packet(PDU& pdu) { payload.begin(), payload.begin() + (seq_number_ - iter->first) ); - store_payload(seq_number_, iter->second); + store_payload(seq_number_, move(iter->second)); iter = erase_iterator(iter); } else { @@ -176,16 +180,16 @@ void Flow::process_packet(PDU& pdu) { } } -void Flow::store_payload(uint32_t seq, const payload_type& payload) { +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()) { - buffered_payload_.insert(make_pair(seq, payload)); + buffered_payload_.insert(make_pair(seq, move(payload))); } else if (iter->second.size() < payload.size()) { // If we already have payload on this position but it's a shorter // chunk than the new one, replace it - iter->second = payload; + iter->second = move(payload); } } @@ -283,6 +287,10 @@ void Flow::state(State new_state) { state_ = new_state; } +void Flow::ignore_data_packets() { + ignore_data_packets_ = true; +} + // Stream Stream::Stream(const PDU& packet) @@ -343,6 +351,14 @@ void Stream::server_buffering_callback(const stream_callback& callback) { on_server_buffering_callback_ = callback; } +void Stream::ignore_client_data() { + client_flow().ignore_data_packets(); +} + +void Stream::ignore_server_data() { + server_flow().ignore_data_packets(); +} + bool Stream::is_finished() const { const Flow::State client_state = client_flow_.state(); const Flow::State server_state = server_flow_.state(); diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index d4f2bd7..52aedf9 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -276,6 +276,22 @@ TEST_F(FlowTest, Overlapping) { run_tests(chunks, payload); } +TEST_F(FlowTest, IgnoreDataPackets) { + using std::placeholders::_1; + + ordering_info_type chunks = split_payload(payload, 5); + Flow flow(IPv4Address("1.2.3.4"), 22, 0); + flow.data_callback(bind(&FlowTest::cumulative_flow_data_handler, this, _1)); + flow.ignore_data_packets(); + vector packets = chunks_to_packets(0, chunks, payload); + for (size_t i = 0; i < packets.size(); ++i) { + flow.process_packet(packets[i]); + } + EXPECT_TRUE(flow_payload_chunks.empty()); +} + +// Stream follower tests + TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { using std::placeholders::_1; From 8db60323032a77c8712d091a923a004b7b188808 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Mon, 8 Feb 2016 20:59:00 -0800 Subject: [PATCH 07/19] Add hardware addresses to Stream --- include/tins/tcp_ip.h | 28 ++++++++++++++++++++++++++++ src/tcp_ip.cpp | 14 ++++++++++++++ tests/src/tcp_ip.cpp | 4 ++++ 3 files changed, 46 insertions(+) diff --git a/include/tins/tcp_ip.h b/include/tins/tcp_ip.h index 0a4e26d..c3d2869 100644 --- a/include/tins/tcp_ip.h +++ b/include/tins/tcp_ip.h @@ -41,6 +41,7 @@ #include #include #include "macros.h" +#include "hw_address.h" namespace Tins { @@ -276,6 +277,11 @@ public: */ typedef std::function stream_callback; + /** + * The type used to store hardware addresses + */ + typedef HWAddress<6> hwaddress_type; + /** * The type used to store payloads */ @@ -341,6 +347,26 @@ public: */ IPv6Address client_addr_v6() const; + /** + * \brief Retrieves the client's hardware address. + * + * Note that this is not the actual hardware address of the client, but + * just the address seen from packets coming from it. If the client + * is on another network, then this will be the address of the last + * device (switch, route, etc) the packet went through. + */ + const hwaddress_type& client_hw_addr() const; + + /** + * \brief Retrieves the server's hardware address. + * + * Note that this is not the actual hardware address of the server, but + * just the address seen from packets coming from it. If the server + * is on another network, then this will be the address of the last + * device (switch, route, etc) the packet went through. + */ + const hwaddress_type& server_hw_addr() const; + /** * \brief Retrieves the server's IPv4 address * @@ -486,6 +512,8 @@ private: stream_callback on_server_data_callback_; stream_callback on_client_buffering_callback_; stream_callback on_server_buffering_callback_; + hwaddress_type client_hw_addr_; + hwaddress_type server_hw_addr_; bool auto_cleanup_; }; diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp index 9a6e490..0059f74 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip.cpp @@ -39,6 +39,7 @@ #include "tcp.h" #include "ip.h" #include "ipv6.h" +#include "ethernetII.h" #include "rawpdu.h" #include "exceptions.h" #include "memory_helpers.h" @@ -301,6 +302,11 @@ Stream::Stream(const PDU& packet) if (tcp.flags() == TCP::SYN) { client_flow().state(Flow::SYN_SENT); } + const EthernetII* eth = packet.find_pdu(); + if (eth) { + client_hw_addr_ = eth->src_addr(); + server_hw_addr_ = eth->dst_addr(); + } } void Stream::process_packet(PDU& packet) { @@ -384,6 +390,14 @@ IPv6Address Stream::client_addr_v6() const { return server_flow().dst_addr_v6(); } +const Stream::hwaddress_type& Stream::client_hw_addr() const { + return client_hw_addr_; +} + +const Stream::hwaddress_type& Stream::server_hw_addr() const { + return server_hw_addr_; +} + IPv4Address Stream::server_addr_v4() const { return client_flow().dst_addr_v4(); } diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index 52aedf9..5e4ffac 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -296,6 +296,8 @@ TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { using std::placeholders::_1; vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + packets[0].src_addr("00:01:02:03:04:05"); + packets[0].dst_addr("05:04:03:02:01:00"); StreamFollower follower; follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); for (size_t i = 0; i < packets.size(); ++i) { @@ -313,6 +315,8 @@ TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { EXPECT_EQ(22, stream.server_flow().dport()); EXPECT_EQ(IPv4Address("1.2.3.4"), stream.client_addr_v4()); EXPECT_EQ(IPv4Address("4.3.2.1"), stream.server_addr_v4()); + EXPECT_EQ(HWAddress<6>("00:01:02:03:04:05"), stream.client_hw_addr()); + EXPECT_EQ(HWAddress<6>("05:04:03:02:01:00"), stream.server_hw_addr()); EXPECT_EQ(22, stream.client_port()); EXPECT_EQ(25, stream.server_port()); From 69fc5ff54bf67fd1953848cb03fdf5097bcb75c6 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Tue, 9 Feb 2016 19:26:51 -0800 Subject: [PATCH 08/19] Add support for out of order data packet detection --- include/tins/tcp_ip.h | 78 ++++++++++++++++++++++++++++--------------- src/tcp_ip.cpp | 61 ++++++++++++++++++--------------- tests/src/tcp_ip.cpp | 37 ++++++++++++++++++-- 3 files changed, 119 insertions(+), 57 deletions(-) diff --git a/include/tins/tcp_ip.h b/include/tins/tcp_ip.h index c3d2869..d72b2ba 100644 --- a/include/tins/tcp_ip.h +++ b/include/tins/tcp_ip.h @@ -94,9 +94,19 @@ public: typedef std::map buffered_payload_type; /** - * The type used to store the callbacks that this class triggers + * The type used to store the callback called when new data is available */ - typedef std::function event_callback; + typedef std::function data_available_callback_type; + + /** + * \brief The type used to store the callback called when data is buffered + * + * The arguments are the flow, the sequence number and payload that will + * be buffered. + */ + typedef std::function out_of_order_callback_type; /** * Construct a Flow from an IPv4 address @@ -126,17 +136,17 @@ public: * * \param callback The callback to be executed */ - void data_callback(const event_callback& callback); + void data_callback(const data_available_callback_type& callback); /** - * \brief Sets the callback that will be executed when data is buffered. + * \brief Sets the callback that will be executed when out of order data arrives * * Whenever this flow receives out-of-order data, this callback will be * executed. * * \param callback The callback to be executed */ - void buffering_callback(const event_callback& callback); + void out_of_order_callback(const out_of_order_callback_type& callback); /** * \brief Processes a packet. @@ -250,8 +260,8 @@ private: uint32_t seq_number_; std::array dest_address_; uint16_t dest_port_; - event_callback on_data_callback_; - event_callback on_buffering_callback_; + data_available_callback_type on_data_callback_; + out_of_order_callback_type on_out_of_order_callback_; State state_; bool is_v6_; bool ignore_data_packets_; @@ -275,17 +285,27 @@ public: /** * The type used for callbacks */ - typedef std::function stream_callback; + typedef std::function stream_callback_type; + + /** + * The type used to store payloads + */ + typedef Flow::payload_type payload_type; + + /** + * The type used for callbacks + * + * /sa Flow::buffering_callback + */ + typedef std::function out_of_order_callback_type; /** * The type used to store hardware addresses */ typedef HWAddress<6> hwaddress_type; - /** - * The type used to store payloads - */ - typedef Flow::payload_type payload_type; /** * \brief Constructs a TCP stream using the provided packet. @@ -416,7 +436,7 @@ public: * * \param callback The callback to be set */ - void stream_closed_callback(const stream_callback& callback); + void stream_closed_callback(const stream_callback_type& callback); /** * \brief Sets the callback to be executed when there's client data @@ -424,7 +444,7 @@ public: * \sa Flow::data_callback * \param callback The callback to be set */ - void client_data_callback(const stream_callback& callback); + void client_data_callback(const stream_callback_type& callback); /** * \brief Sets the callback to be executed when there's server data @@ -432,7 +452,7 @@ public: * \sa Flow::data_callback * \param callback The callback to be set */ - void server_data_callback(const stream_callback& callback); + void server_data_callback(const stream_callback_type& callback); /** * \brief Sets the callback to be executed when there's new buffered @@ -441,7 +461,7 @@ public: * \sa Flow::buffering_callback * \param callback The callback to be set */ - void client_buffering_callback(const stream_callback& callback); + void client_out_of_order_callback(const out_of_order_callback_type& callback); /** * \brief Sets the callback to be executed when there's new buffered @@ -450,7 +470,7 @@ public: * \sa Flow::buffering_callback * \param callback The callback to be set */ - void server_buffering_callback(const stream_callback& callback); + void server_out_of_order_callback(const out_of_order_callback_type& callback); /** * \brief Indicates that the data packets sent by the client should be @@ -502,16 +522,20 @@ private: void on_client_flow_data(const Flow& flow); void on_server_flow_data(const Flow& flow); - void on_client_buffering(const Flow& flow); - void on_server_buffering(const Flow& flow); + void on_client_out_of_order(const Flow& flow, + uint32_t seq, + const payload_type& payload); + void on_server_out_of_order(const Flow& flow, + uint32_t seq, + const payload_type& payload); Flow client_flow_; Flow server_flow_; - stream_callback on_stream_closed_; - stream_callback on_client_data_callback_; - stream_callback on_server_data_callback_; - stream_callback on_client_buffering_callback_; - stream_callback on_server_buffering_callback_; + stream_callback_type on_stream_closed_; + stream_callback_type on_client_data_callback_; + stream_callback_type on_server_data_callback_; + out_of_order_callback_type on_client_out_of_order_callback_; + out_of_order_callback_type on_server_out_of_order_callback_; hwaddress_type client_hw_addr_; hwaddress_type server_hw_addr_; bool auto_cleanup_; @@ -546,7 +570,7 @@ public: /** * \brief The type used for callbacks */ - typedef Stream::stream_callback stream_callback; + typedef Stream::stream_callback_type stream_callback_type; /** * Default constructor @@ -576,7 +600,7 @@ public: * * \param callback The callback to be set */ - void new_stream_callback(const stream_callback& callback); + void new_stream_callback(const stream_callback_type& callback); /** * Finds the stream identified by the provided arguments. @@ -625,7 +649,7 @@ private: static address_type serialize(const IPv6Address& address); streams_type streams_; - stream_callback on_new_connection_; + stream_callback_type on_new_connection_; size_t max_buffered_chunks_; bool attach_to_flows_; }; diff --git a/src/tcp_ip.cpp b/src/tcp_ip.cpp index 0059f74..7b93db1 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip.cpp @@ -92,12 +92,12 @@ Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, output.write(dest_address); } -void Flow::data_callback(const event_callback& callback) { +void Flow::data_callback(const data_available_callback_type& callback) { on_data_callback_ = callback; } -void Flow::buffering_callback(const event_callback& callback) { - on_buffering_callback_= callback; +void Flow::out_of_order_callback(const out_of_order_callback_type& callback) { + on_out_of_order_callback_ = callback; } void Flow::process_packet(PDU& pdu) { @@ -118,6 +118,11 @@ void Flow::process_packet(PDU& pdu) { if (seq_compare(chunk_end, seq_number_) >= 0) { bool added_some = false; 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_) { + 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; @@ -173,11 +178,6 @@ void Flow::process_packet(PDU& pdu) { on_data_callback_(*this); } } - else { - if (on_buffering_callback_) { - on_buffering_callback_(*this); - } - } } } @@ -337,24 +337,24 @@ const Flow& Stream::server_flow() const { return server_flow_; } -void Stream::stream_closed_callback(const stream_callback& callback) { +void Stream::stream_closed_callback(const stream_callback_type& callback) { on_stream_closed_ = callback; } -void Stream::client_data_callback(const stream_callback& callback) { +void Stream::client_data_callback(const stream_callback_type& callback) { on_client_data_callback_ = callback; } -void Stream::server_data_callback(const stream_callback& callback) { +void Stream::server_data_callback(const stream_callback_type& callback) { on_server_data_callback_ = callback; } -void Stream::client_buffering_callback(const stream_callback& callback) { - on_client_buffering_callback_ = callback; +void Stream::client_out_of_order_callback(const out_of_order_callback_type& callback) { + on_client_out_of_order_callback_ = callback; } -void Stream::server_buffering_callback(const stream_callback& callback) { - on_server_buffering_callback_ = callback; +void Stream::server_out_of_order_callback(const out_of_order_callback_type& callback) { + on_server_out_of_order_callback_ = callback; } void Stream::ignore_client_data() { @@ -467,11 +467,14 @@ Flow Stream::extract_server_flow(const PDU& packet) { } void Stream::setup_flows_callbacks() { - using std::placeholders::_1; + using namespace std::placeholders; + client_flow_.data_callback(bind(&Stream::on_client_flow_data, this, _1)); server_flow_.data_callback(bind(&Stream::on_server_flow_data, this, _1)); - client_flow_.buffering_callback(bind(&Stream::on_client_buffering, this, _1)); - server_flow_.buffering_callback(bind(&Stream::on_server_buffering, this, _1)); + client_flow_.out_of_order_callback(bind(&Stream::on_client_out_of_order, + this, _1, _2, _3)); + server_flow_.out_of_order_callback(bind(&Stream::on_server_out_of_order, + this, _1, _2, _3)); } void Stream::auto_cleanup_payloads(bool value) { @@ -496,15 +499,19 @@ void Stream::on_server_flow_data(const Flow& /*flow*/) { } } -void Stream::on_client_buffering(const Flow& flow) { - if (on_client_buffering_callback_) { - on_client_buffering_callback_(*this); +void Stream::on_client_out_of_order(const Flow& flow, + uint32_t seq, + const payload_type& payload) { + if (on_client_out_of_order_callback_) { + on_client_out_of_order_callback_(*this, seq, payload); } } -void Stream::on_server_buffering(const Flow& flow) { - if (on_server_buffering_callback_) { - on_server_buffering_callback_(*this); +void Stream::on_server_out_of_order(const Flow& flow, + uint32_t seq, + const payload_type& payload) { + if (on_server_out_of_order_callback_) { + on_server_out_of_order_callback_(*this, seq, payload); } } @@ -562,7 +569,7 @@ bool StreamFollower::process_packet(PDU& packet) { return true; } -void StreamFollower::new_stream_callback(const stream_callback& callback) { +void StreamFollower::new_stream_callback(const stream_callback_type& callback) { on_new_connection_ = callback; } @@ -633,7 +640,7 @@ StreamFollower::stream_id::stream_id(const address_type& client_addr, const address_type& server_addr, uint16_t server_port) : min_address(client_addr), max_address(server_addr), min_address_port(client_port), -max_address_port(server_port) { + max_address_port(server_port) { if (min_address > max_address) { swap(min_address, max_address); swap(min_address_port, max_address_port); @@ -648,4 +655,4 @@ bool StreamFollower::stream_id::operator<(const stream_id& rhs) const { } // TCPIP } // Tins -#endif // TINS_IS_CXX11 \ No newline at end of file +#endif // TINS_IS_CXX11 diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index 5e4ffac..d31246a 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -45,7 +45,7 @@ public: void on_new_stream(Stream& stream); void cumulative_stream_client_data_handler(Stream& stream); void cumulative_stream_server_data_handler(Stream& stream); - void buffered_payload_handle(Flow& session); + void out_of_order_handler(Flow& session, uint32_t seq, Flow::payload_type payload); void run_test(uint32_t initial_seq, const ordering_info_type& chunks, const string& payload); void run_test(uint32_t initial_seq, const ordering_info_type& chunks); @@ -64,6 +64,7 @@ public: uint16_t dst_port); vector flow_payload_chunks; + vector > flow_out_of_order_chunks; vector stream_client_payload_chunks; vector stream_server_payload_chunks; }; @@ -127,8 +128,8 @@ void FlowTest::cumulative_stream_server_data_handler(Stream& stream) { stream_server_payload_chunks.push_back(stream.server_flow().payload()); } -void FlowTest::buffered_payload_handle(Flow& session) { - +void FlowTest::out_of_order_handler(Flow& session, uint32_t seq, Flow::payload_type payload) { + flow_out_of_order_chunks.push_back(make_pair(seq, move(payload))); } void FlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks, @@ -290,6 +291,36 @@ TEST_F(FlowTest, IgnoreDataPackets) { EXPECT_TRUE(flow_payload_chunks.empty()); } +TEST_F(FlowTest, OutOfOrderCallback) { + using namespace std::placeholders; + + ordering_info_type chunks = split_payload(payload, 5); + Flow flow(IPv4Address("1.2.3.4"), 22, 0); + flow.out_of_order_callback(bind(&FlowTest::out_of_order_handler, this, _1, _2, _3)); + vector packets = chunks_to_packets(0, chunks, payload); + reverse(packets.begin(), packets.end()); + // Copy, as Flow::process_packet takes ownership of the payload + vector original_packets = packets; + for (size_t i = 0; i < packets.size(); ++i) { + flow.process_packet(packets[i]); + } + // All elements should be out of order except the first + // one (last one in reverse order) + ASSERT_EQ(original_packets.size() - 1, flow_out_of_order_chunks.size()); + for (size_t i = 0; i < original_packets.size() - 1; ++i) { + // Compare sequence number + EXPECT_EQ( + original_packets[i].rfind_pdu().seq(), + flow_out_of_order_chunks[i].first + ); + // Compare payload + EXPECT_EQ( + original_packets[i].rfind_pdu().payload(), + flow_out_of_order_chunks[i].second + ); + } +} + // Stream follower tests TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { From 3b848060aa0920b7f223c5c9eb313c19646be0ac Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Tue, 9 Feb 2016 19:56:30 -0800 Subject: [PATCH 09/19] Change tcp_ip directory structure --- examples/CMakeLists.txt | 4 +- examples/{http_dump.cpp => stream_dump.cpp} | 14 +- include/tins/CMakeLists.txt | 1 + include/tins/tcp_ip/CMakeLists.txt | 6 + include/tins/{tcp_ip.h => tcp_ip/stream.h} | 125 +----------- include/tins/tcp_ip/stream_follower.h | 168 ++++++++++++++++ src/CMakeLists.txt | 3 +- src/{tcp_ip.cpp => tcp_ip/stream.cpp} | 139 +------------- src/tcp_ip/stream_follower.cpp | 202 ++++++++++++++++++++ tests/src/tcp_ip.cpp | 2 +- 10 files changed, 399 insertions(+), 265 deletions(-) rename examples/{http_dump.cpp => stream_dump.cpp} (92%) create mode 100644 include/tins/tcp_ip/CMakeLists.txt rename include/tins/{tcp_ip.h => tcp_ip/stream.h} (81%) create mode 100644 include/tins/tcp_ip/stream_follower.h rename src/{tcp_ip.cpp => tcp_ip/stream.cpp} (75%) create mode 100644 src/tcp_ip/stream_follower.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8baf71e..3e2a9bf 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,7 +15,7 @@ IF(libtins_FOUND) dns_queries dns_spoof dns_stats - http_dump + stream_dump icmp_responses interfaces_info tcp_connection_close @@ -41,7 +41,7 @@ IF(libtins_FOUND) ADD_EXECUTABLE(arpmonitor EXCLUDE_FROM_ALL arpmonitor.cpp) ADD_EXECUTABLE(dns_queries EXCLUDE_FROM_ALL dns_queries.cpp) ADD_EXECUTABLE(dns_spoof EXCLUDE_FROM_ALL dns_spoof.cpp) - ADD_EXECUTABLE(http_dump EXCLUDE_FROM_ALL http_dump.cpp) + ADD_EXECUTABLE(stream_dump EXCLUDE_FROM_ALL stream_dump.cpp) ADD_EXECUTABLE(icmp_responses EXCLUDE_FROM_ALL icmp_responses.cpp) ADD_EXECUTABLE(interfaces_info EXCLUDE_FROM_ALL interfaces_info.cpp) ADD_EXECUTABLE(tcp_connection_close EXCLUDE_FROM_ALL tcp_connection_close.cpp) diff --git a/examples/http_dump.cpp b/examples/stream_dump.cpp similarity index 92% rename from examples/http_dump.cpp rename to examples/stream_dump.cpp index 5daad0a..d76ee70 100644 --- a/examples/http_dump.cpp +++ b/examples/stream_dump.cpp @@ -29,7 +29,7 @@ #include #include -#include "tins/tcp_ip.h" +#include "tins/tcp_ip/stream_follower.h" #include "tins/sniffer.h" #include "tins/ip_address.h" #include "tins/ipv6_address.h" @@ -47,6 +47,11 @@ using Tins::SnifferConfiguration; using Tins::TCPIP::StreamFollower; using Tins::TCPIP::Stream; +// This example takes an interface and a port as an argument and +// it listens for TCP streams on the given interface and port. +// It will reassemble TCP streams and show the traffic sent by +// both the client and the server. + // Convert the client endpoint to a readable string string client_endpoint(const Stream& stream) { ostringstream output; @@ -94,6 +99,7 @@ void on_client_data(Stream& stream) { // Whenever there's new server data on the stream, this callback is executed. // This does the same thing as on_client_data void on_server_data(Stream& stream) { + std::cout << "server data\n"; string data(stream.server_payload().begin(), stream.server_payload().end()); cout << server_endpoint(stream) << " >> " << client_endpoint(stream) << ": " << endl << data << endl; @@ -118,8 +124,8 @@ void on_new_connection(Stream& stream) { } int main(int argc, char* argv[]) { - if (argc != 2) { - cout << "Usage: " << argv[0] << " " << endl; + if (argc != 3) { + cout << "Usage: " << argv[0] << " " << endl; return 1; } using std::placeholders::_1; @@ -128,7 +134,7 @@ int main(int argc, char* argv[]) { // Construct the sniffer configuration object SnifferConfiguration config; // Only capture TCP traffic sent from/to port 80 - config.set_filter("tcp port 80"); + config.set_filter("tcp port " + string(argv[2])); // Construct the sniffer we'll use Sniffer sniffer(argv[1], config); diff --git a/include/tins/CMakeLists.txt b/include/tins/CMakeLists.txt index 3737ced..54f8c42 100644 --- a/include/tins/CMakeLists.txt +++ b/include/tins/CMakeLists.txt @@ -5,3 +5,4 @@ INSTALL( COMPONENT Headers ) ADD_SUBDIRECTORY(dot11) +ADD_SUBDIRECTORY(tcp_ip) diff --git a/include/tins/tcp_ip/CMakeLists.txt b/include/tins/tcp_ip/CMakeLists.txt new file mode 100644 index 0000000..db9c3a7 --- /dev/null +++ b/include/tins/tcp_ip/CMakeLists.txt @@ -0,0 +1,6 @@ +FILE(GLOB INCLUDE_FILES "*.h") +INSTALL( + FILES ${INCLUDE_FILES} + DESTINATION include/tins/tcp_ip + COMPONENT Headers +) diff --git a/include/tins/tcp_ip.h b/include/tins/tcp_ip/stream.h similarity index 81% rename from include/tins/tcp_ip.h rename to include/tins/tcp_ip/stream.h index d72b2ba..bdd9e4e 100644 --- a/include/tins/tcp_ip.h +++ b/include/tins/tcp_ip/stream.h @@ -27,10 +27,10 @@ * */ -#ifndef TINS_TCP_IP_H -#define TINS_TCP_IP_H +#ifndef TINS_TCP_IP_STREAM_H +#define TINS_TCP_IP_STREAM_H -#include "cxxstd.h" +#include "../cxxstd.h" // This classes use C++11 features #if TINS_IS_CXX11 @@ -40,8 +40,8 @@ #include #include #include -#include "macros.h" -#include "hw_address.h" +#include "../macros.h" +#include "../hw_address.h" namespace Tins { @@ -541,122 +541,9 @@ private: bool auto_cleanup_; }; -/** - * \brief Represents a class that follows TCP and reassembles streams - * - * This class processes packets and whenever it detects a new connection - * being open, it starts tracking it. This will follow all data sent by - * each peer and make it available to the user in a simple way. - * - * In order to use this class, just create an instance and set the - * new stream callback to some function that you want: - * - * \code - * void on_new_stream(TCPStream& stream) { - * // Do something with it. - * // This is the perfect time to set the stream's client/server - * // write callbacks so you are notified whenever there's new - * // data on the stream - * } - * - * // Create it - * StreamFollower follower; - * // Set the callback - * follower.new_stream_callback(&on_new_stream); - * \endcode - */ -class TINS_API StreamFollower { -public: - /** - * \brief The type used for callbacks - */ - typedef Stream::stream_callback_type stream_callback_type; - - /** - * Default constructor - */ - StreamFollower(); - - /** - * \brief Processes a packet - * - * This will detect if this packet belongs to an existing stream - * and process it, or if it belongs to a new one, in which case it - * starts tracking it. - * - * This method always returns true so it can be easily plugged as - * the argument to Sniffer::sniff_loop. - * - * \param packet The packet to be processed - * \return Always true - */ - bool process_packet(PDU& packet); - - /** - * \brief Sets the callback to be executed when a new stream is captured. - * - * Whenever a new stream is captured, the provided callback will be - * executed. - * - * \param callback The callback to be set - */ - void new_stream_callback(const stream_callback_type& callback); - - /** - * Finds the stream identified by the provided arguments. - * - * \param client_addr The client's address - * \param client_port The client's port - * \param server_addr The server's address - * \param server_addr The server's port - */ - Stream& find_stream(IPv4Address client_addr, uint16_t client_port, - IPv4Address server_addr, uint16_t server_port); - - /** - * Finds the stream identified by the provided arguments. - * - * \param client_addr The client's address - * \param client_port The client's port - * \param server_addr The server's address - * \param server_addr The server's port - */ - Stream& find_stream(IPv6Address client_addr, uint16_t client_port, - IPv6Address server_addr, uint16_t server_port); -private: - static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; - typedef std::array address_type; - - struct stream_id { - stream_id(const address_type& client_addr, uint16_t client_port, - const address_type& server_addr, uint16_t server_port); - - address_type min_address; - address_type max_address; - uint16_t min_address_port; - uint16_t max_address_port; - - bool operator<(const stream_id& rhs) const; - - static size_t hash(const stream_id& id); - }; - - typedef std::map streams_type; - - stream_id make_stream_id(const PDU& packet); - Stream& find_stream(const stream_id& id); - static address_type serialize(IPv4Address address); - static address_type serialize(const IPv6Address& address); - - streams_type streams_; - stream_callback_type on_new_connection_; - size_t max_buffered_chunks_; - bool attach_to_flows_; -}; - } // TCPIP } // Tins #endif // TINS_IS_CXX11 -#endif // TINS_TCP_IP_H +#endif // TINS_TCP_IP_STREAM_H diff --git a/include/tins/tcp_ip/stream_follower.h b/include/tins/tcp_ip/stream_follower.h new file mode 100644 index 0000000..a277f43 --- /dev/null +++ b/include/tins/tcp_ip/stream_follower.h @@ -0,0 +1,168 @@ +/* + * 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_STREAM_FOLLOWER_H +#define TINS_TCP_IP_STREAM_FOLLOWER_H + +#include "../cxxstd.h" + +// This classes use C++11 features +#if TINS_IS_CXX11 + +#include +#include "stream.h" + +namespace Tins { + +class PDU; +class TCP; +class IPv4Address; +class IPv6Address; + +namespace TCPIP { + +/** + * \brief Represents a class that follows TCP and reassembles streams + * + * This class processes packets and whenever it detects a new connection + * being open, it starts tracking it. This will follow all data sent by + * each peer and make it available to the user in a simple way. + * + * In order to use this class, just create an instance and set the + * new stream callback to some function that you want: + * + * \code + * void on_new_stream(TCPStream& stream) { + * // Do something with it. + * // This is the perfect time to set the stream's client/server + * // write callbacks so you are notified whenever there's new + * // data on the stream + * } + * + * // Create it + * StreamFollower follower; + * // Set the callback + * follower.new_stream_callback(&on_new_stream); + * \endcode + */ +class TINS_API StreamFollower { +public: + /** + * \brief The type used for callbacks + */ + typedef Stream::stream_callback_type stream_callback_type; + + /** + * Default constructor + */ + StreamFollower(); + + /** + * \brief Processes a packet + * + * This will detect if this packet belongs to an existing stream + * and process it, or if it belongs to a new one, in which case it + * starts tracking it. + * + * This method always returns true so it can be easily plugged as + * the argument to Sniffer::sniff_loop. + * + * \param packet The packet to be processed + * \return Always true + */ + bool process_packet(PDU& packet); + + /** + * \brief Sets the callback to be executed when a new stream is captured. + * + * Whenever a new stream is captured, the provided callback will be + * executed. + * + * \param callback The callback to be set + */ + void new_stream_callback(const stream_callback_type& callback); + + /** + * Finds the stream identified by the provided arguments. + * + * \param client_addr The client's address + * \param client_port The client's port + * \param server_addr The server's address + * \param server_addr The server's port + */ + Stream& find_stream(IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port); + + /** + * Finds the stream identified by the provided arguments. + * + * \param client_addr The client's address + * \param client_port The client's port + * \param server_addr The server's address + * \param server_addr The server's port + */ + Stream& find_stream(IPv6Address client_addr, uint16_t client_port, + IPv6Address server_addr, uint16_t server_port); +private: + static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; + typedef std::array address_type; + + struct stream_id { + stream_id(const address_type& client_addr, uint16_t client_port, + const address_type& server_addr, uint16_t server_port); + + address_type min_address; + address_type max_address; + uint16_t min_address_port; + uint16_t max_address_port; + + bool operator<(const stream_id& rhs) const; + + static size_t hash(const stream_id& id); + }; + + typedef std::map streams_type; + + stream_id make_stream_id(const PDU& packet); + Stream& find_stream(const stream_id& id); + static address_type serialize(IPv4Address address); + static address_type serialize(const IPv6Address& address); + + streams_type streams_; + stream_callback_type on_new_connection_; + size_t max_buffered_chunks_; + bool attach_to_flows_; +}; + +} // TCPIP +} // Tins + +#endif // TINS_IS_CXX11 + +#endif // TINS_TCP_IP_STREAM_FOLLOWER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bc3da02..d436f0d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,7 +53,8 @@ ADD_LIBRARY( snap.cpp sniffer.cpp tcp.cpp - tcp_ip.cpp + tcp_ip/stream.cpp + tcp_ip/stream_follower.cpp tcp_stream.cpp udp.cpp utils.cpp diff --git a/src/tcp_ip.cpp b/src/tcp_ip/stream.cpp similarity index 75% rename from src/tcp_ip.cpp rename to src/tcp_ip/stream.cpp index 7b93db1..abfd097 100644 --- a/src/tcp_ip.cpp +++ b/src/tcp_ip/stream.cpp @@ -27,7 +27,7 @@ * */ -#include "tcp_ip.h" +#include "tcp_ip/stream.h" #if TINS_IS_CXX11 @@ -515,143 +515,6 @@ void Stream::on_server_out_of_order(const Flow& flow, } } -// StreamFollower - -const size_t StreamFollower::DEFAULT_MAX_BUFFERED_CHUNKS = 512; - -StreamFollower::StreamFollower() -: max_buffered_chunks_(DEFAULT_MAX_BUFFERED_CHUNKS), attach_to_flows_(false) { - -} - -bool StreamFollower::process_packet(PDU& packet) { - stream_id identifier = make_stream_id(packet); - streams_type::iterator iter = streams_.find(identifier); - bool process = true; - if (iter == streams_.end()) { - const TCP& tcp = packet.rfind_pdu(); - // Start tracking if they're either SYNs or they contain data (attach - // to an already running flow). - if (tcp.flags() == TCP::SYN || (attach_to_flows_ && tcp.find_pdu() != 0)) { - iter = streams_.insert(make_pair(identifier, Stream(packet))).first; - iter->second.setup_flows_callbacks(); - if (on_new_connection_) { - on_new_connection_(iter->second); - } - else { - // TODO: use proper exception - throw runtime_error("No new connection callback set"); - } - if (tcp.flags() == TCP::SYN) { - process = false; - } - else { - // Otherwise, assume the connection is established - iter->second.client_flow().state(Flow::ESTABLISHED); - iter->second.server_flow().state(Flow::ESTABLISHED); - } - } - else { - process = false; - } - } - // We'll process it if we had already seen this stream or if we just attached to - // it and it contains payload - if (process) { - Stream& stream = iter->second; - stream.process_packet(packet); - size_t total_chunks = stream.client_flow().buffered_payload().size() + - stream.server_flow().buffered_payload().size(); - if (stream.is_finished() || total_chunks > max_buffered_chunks_) { - streams_.erase(iter); - } - } - return true; -} - -void StreamFollower::new_stream_callback(const stream_callback_type& callback) { - on_new_connection_ = callback; -} - -Stream& StreamFollower::find_stream(IPv4Address client_addr, uint16_t client_port, - IPv4Address server_addr, uint16_t server_port) { - stream_id identifier(serialize(client_addr), client_port, - serialize(server_addr), server_port); - return find_stream(identifier); -} - -Stream& StreamFollower::find_stream(IPv6Address client_addr, uint16_t client_port, - IPv6Address server_addr, uint16_t server_port) { - stream_id identifier(serialize(client_addr), client_port, - serialize(server_addr), server_port); - return find_stream(identifier); -} - -StreamFollower::stream_id StreamFollower::make_stream_id(const PDU& packet) { - const TCP* tcp = packet.find_pdu(); - if (!tcp) { - // TODO: define proper exception - throw runtime_error("No TCP"); - } - if (const IP* ip = packet.find_pdu()) { - return stream_id(serialize(ip->src_addr()), tcp->sport(), - serialize(ip->dst_addr()), tcp->dport()); - } - else if (const IPv6* ip = packet.find_pdu()) { - return stream_id(serialize(ip->src_addr()), tcp->sport(), - serialize(ip->dst_addr()), tcp->dport()); - } - else { - // TODO: define proper exception - throw runtime_error("No layer 3"); - } -} - -Stream& StreamFollower::find_stream(const stream_id& id) { - streams_type::iterator iter = streams_.find(id); - if (iter == streams_.end()) { - throw stream_not_found(); - } - else { - return iter->second; - } -} - -StreamFollower::address_type StreamFollower::serialize(IPv4Address address) { - address_type addr; - OutputMemoryStream output(addr.data(), addr.size()); - addr.fill(0); - output.write(address); - return addr; -} - -StreamFollower::address_type StreamFollower::serialize(const IPv6Address& address) { - address_type addr; - OutputMemoryStream output(addr.data(), addr.size()); - addr.fill(0); - output.write(address); - return addr; -} - -// stream_id - -StreamFollower::stream_id::stream_id(const address_type& client_addr, - uint16_t client_port, - const address_type& server_addr, - uint16_t server_port) -: min_address(client_addr), max_address(server_addr), min_address_port(client_port), - max_address_port(server_port) { - if (min_address > max_address) { - swap(min_address, max_address); - swap(min_address_port, max_address_port); - } -} - -bool StreamFollower::stream_id::operator<(const stream_id& rhs) const { - return tie(min_address, min_address_port, max_address, max_address_port) < - tie(rhs.min_address, rhs.min_address_port, rhs.max_address, rhs.max_address_port); -} - } // TCPIP } // Tins diff --git a/src/tcp_ip/stream_follower.cpp b/src/tcp_ip/stream_follower.cpp new file mode 100644 index 0000000..b7b2404 --- /dev/null +++ b/src/tcp_ip/stream_follower.cpp @@ -0,0 +1,202 @@ +/* + * 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/stream_follower.h" + +#if TINS_IS_CXX11 + +#include +#include +#include "memory.h" +#include "ip_address.h" +#include "ipv6_address.h" +#include "tcp.h" +#include "ip.h" +#include "ipv6.h" +#include "rawpdu.h" +#include "exceptions.h" +#include "memory_helpers.h" + +using std::make_pair; +using std::bind; +using std::pair; +using std::runtime_error; +using std::numeric_limits; +using std::max; +using std::swap; + +using Tins::Memory::OutputMemoryStream; +using Tins::Memory::InputMemoryStream; + +namespace Tins { +namespace TCPIP { + +const size_t StreamFollower::DEFAULT_MAX_BUFFERED_CHUNKS = 512; + +StreamFollower::StreamFollower() +: max_buffered_chunks_(DEFAULT_MAX_BUFFERED_CHUNKS), attach_to_flows_(false) { + +} + +bool StreamFollower::process_packet(PDU& packet) { + stream_id identifier = make_stream_id(packet); + streams_type::iterator iter = streams_.find(identifier); + bool process = true; + if (iter == streams_.end()) { + const TCP& tcp = packet.rfind_pdu(); + // Start tracking if they're either SYNs or they contain data (attach + // to an already running flow). + if (tcp.flags() == TCP::SYN || (attach_to_flows_ && tcp.find_pdu() != 0)) { + iter = streams_.insert(make_pair(identifier, Stream(packet))).first; + iter->second.setup_flows_callbacks(); + if (on_new_connection_) { + on_new_connection_(iter->second); + } + else { + // TODO: use proper exception + throw runtime_error("No new connection callback set"); + } + if (tcp.flags() == TCP::SYN) { + process = false; + } + else { + // Otherwise, assume the connection is established + iter->second.client_flow().state(Flow::ESTABLISHED); + iter->second.server_flow().state(Flow::ESTABLISHED); + } + } + else { + process = false; + } + } + // We'll process it if we had already seen this stream or if we just attached to + // it and it contains payload + if (process) { + Stream& stream = iter->second; + stream.process_packet(packet); + size_t total_chunks = stream.client_flow().buffered_payload().size() + + stream.server_flow().buffered_payload().size(); + if (stream.is_finished() || total_chunks > max_buffered_chunks_) { + streams_.erase(iter); + } + } + return true; +} + +void StreamFollower::new_stream_callback(const stream_callback_type& callback) { + on_new_connection_ = callback; +} + +Stream& StreamFollower::find_stream(IPv4Address client_addr, uint16_t client_port, + IPv4Address server_addr, uint16_t server_port) { + stream_id identifier(serialize(client_addr), client_port, + serialize(server_addr), server_port); + return find_stream(identifier); +} + +Stream& StreamFollower::find_stream(IPv6Address client_addr, uint16_t client_port, + IPv6Address server_addr, uint16_t server_port) { + stream_id identifier(serialize(client_addr), client_port, + serialize(server_addr), server_port); + return find_stream(identifier); +} + +StreamFollower::stream_id StreamFollower::make_stream_id(const PDU& packet) { + const TCP* tcp = packet.find_pdu(); + if (!tcp) { + // TODO: define proper exception + throw runtime_error("No TCP"); + } + if (const IP* ip = packet.find_pdu()) { + return stream_id(serialize(ip->src_addr()), tcp->sport(), + serialize(ip->dst_addr()), tcp->dport()); + } + else if (const IPv6* ip = packet.find_pdu()) { + return stream_id(serialize(ip->src_addr()), tcp->sport(), + serialize(ip->dst_addr()), tcp->dport()); + } + else { + // TODO: define proper exception + throw runtime_error("No layer 3"); + } +} + +Stream& StreamFollower::find_stream(const stream_id& id) { + streams_type::iterator iter = streams_.find(id); + if (iter == streams_.end()) { + throw stream_not_found(); + } + else { + return iter->second; + } +} + +StreamFollower::address_type StreamFollower::serialize(IPv4Address address) { + address_type addr; + OutputMemoryStream output(addr.data(), addr.size()); + addr.fill(0); + output.write(address); + return addr; +} + +StreamFollower::address_type StreamFollower::serialize(const IPv6Address& address) { + address_type addr; + OutputMemoryStream output(addr.data(), addr.size()); + addr.fill(0); + output.write(address); + return addr; +} + +// stream_id + +StreamFollower::stream_id::stream_id(const address_type& client_addr, + uint16_t client_port, + const address_type& server_addr, + uint16_t server_port) +: min_address(client_addr), max_address(server_addr), min_address_port(client_port), + max_address_port(server_port) { + if (min_address > max_address) { + swap(min_address, max_address); + swap(min_address_port, max_address_port); + } + else if (min_address == max_address && min_address_port > max_address_port) { + // If the address is the same, just sort ports + swap(min_address_port, max_address_port); + } +} + +bool StreamFollower::stream_id::operator<(const stream_id& rhs) const { + return tie(min_address, min_address_port, max_address, max_address_port) < + tie(rhs.min_address, rhs.min_address_port, rhs.max_address, rhs.max_address_port); +} + +} // TCPIP +} // Tins + +#endif // TINS_IS_CXX11 diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index d31246a..9163aa2 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -8,7 +8,7 @@ #include #include #include -#include "tcp_ip.h" +#include "tcp_ip/stream_follower.h" #include "tcp.h" #include "ip.h" #include "ip_address.h" From 85d7401520cf2baeb6d5b0a3182feabfdd36fd56 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Wed, 10 Feb 2016 21:24:15 -0800 Subject: [PATCH 10/19] Store MSS value on Flows --- examples/stream_dump.cpp | 1 - include/tins/tcp_ip/stream.h | 23 ++++++++++++++++++++--- src/tcp_ip/stream.cpp | 31 ++++++++++++++++++------------- tests/src/tcp_ip.cpp | 20 ++++++++++++++++++++ 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/examples/stream_dump.cpp b/examples/stream_dump.cpp index d76ee70..66353ae 100644 --- a/examples/stream_dump.cpp +++ b/examples/stream_dump.cpp @@ -99,7 +99,6 @@ void on_client_data(Stream& stream) { // Whenever there's new server data on the stream, this callback is executed. // This does the same thing as on_client_data void on_server_data(Stream& stream) { - std::cout << "server data\n"; string data(stream.server_payload().begin(), stream.server_payload().end()); cout << server_endpoint(stream) << " >> " << client_endpoint(stream) << ": " << endl << data << endl; diff --git a/include/tins/tcp_ip/stream.h b/include/tins/tcp_ip/stream.h index bdd9e4e..7495567 100644 --- a/include/tins/tcp_ip/stream.h +++ b/include/tins/tcp_ip/stream.h @@ -250,7 +250,24 @@ public: * followed to keep track of its state. */ void ignore_data_packets(); + + /** + * \brief Returns the MSS for this Flow. + * + * If the MSS option wasn't provided by the peer, -1 is returned + */ + int mss() const; private: + // Compress all flags into just one struct using bitfields + struct flags { + flags() : ignore_data_packets(0) { + + } + + uint32_t is_v6:1, + ignore_data_packets: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); @@ -263,8 +280,8 @@ private: data_available_callback_type on_data_callback_; out_of_order_callback_type on_out_of_order_callback_; State state_; - bool is_v6_; - bool ignore_data_packets_; + int mss_; + flags flags_; }; /** @@ -310,7 +327,7 @@ public: /** * \brief Constructs a TCP stream using the provided packet. */ - Stream(const PDU& initial_packet); + Stream(PDU& initial_packet); /** * \brief Processes this packet. diff --git a/src/tcp_ip/stream.cpp b/src/tcp_ip/stream.cpp index abfd097..93c109f 100644 --- a/src/tcp_ip/stream.cpp +++ b/src/tcp_ip/stream.cpp @@ -78,18 +78,18 @@ int seq_compare(uint32_t seq1, uint32_t seq2) { Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), - is_v6_(false), ignore_data_packets_(false) { +: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), mss_(-1) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); + flags_.is_v6 = false; } Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), - is_v6_(true), ignore_data_packets_(false) { +: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), mss_(-1) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); + flags_.is_v6 = true; } void Flow::data_callback(const data_available_callback_type& callback) { @@ -107,7 +107,7 @@ void Flow::process_packet(PDU& pdu) { if (tcp) { update_state(*tcp); } - if (ignore_data_packets_) { + if (flags_.ignore_data_packets) { return; } if (!tcp || !raw) { @@ -218,11 +218,15 @@ void Flow::update_state(const TCP& tcp) { else if (state_ == UNKNOWN && (tcp.flags() & TCP::SYN) != 0) { state_ = SYN_SENT; seq_number_ = tcp.seq(); + const TCP::option* mss_option = tcp.search_option(TCP::MSS); + if (mss_option) { + mss_ = mss_option->to(); + } } } bool Flow::is_v6() const { - return is_v6_; + return flags_.is_v6; } bool Flow::is_finished() const { @@ -289,19 +293,20 @@ void Flow::state(State new_state) { } void Flow::ignore_data_packets() { - ignore_data_packets_ = true; + flags_.ignore_data_packets = true; +} + +int Flow::mss() const { + return mss_; } // Stream -Stream::Stream(const PDU& packet) +Stream::Stream(PDU& packet) : client_flow_(extract_client_flow(packet)), server_flow_(extract_server_flow(packet)), auto_cleanup_(true) { - const TCP& tcp = packet.rfind_pdu(); - // If it's a SYN, set the proper state - if (tcp.flags() == TCP::SYN) { - client_flow().state(Flow::SYN_SENT); - } + // Update client flow state + client_flow().process_packet(packet); const EthernetII* eth = packet.find_pdu(); if (eth) { client_hw_addr_ = eth->src_addr(); diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index 9163aa2..616649e 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -359,6 +359,26 @@ TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { EXPECT_EQ(61, stream.server_flow().sequence_number()); } +TEST_F(FlowTest, StreamFollower_MSS) { + using std::placeholders::_1; + + vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + // Client's mss is 1220 + packets[0].rfind_pdu().mss(1220); + // Server's mss is 1460 + packets[1].rfind_pdu().mss(1460); + StreamFollower follower; + follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); + for (size_t i = 0; i < packets.size(); ++i) { + follower.process_packet(packets[i]); + } + Stream& stream = follower.find_stream(IPv4Address("1.2.3.4"), 22, + IPv4Address("4.3.2.1"), 25); + EXPECT_EQ(1220, stream.client_flow().mss()); + EXPECT_EQ(1460, stream.server_flow().mss()); +} + + TEST_F(FlowTest, StreamFollower_RSTClosesStream) { using std::placeholders::_1; From 20a3868e82c83ecba038c3799dd670f89177908f Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Thu, 11 Feb 2016 21:18:48 -0800 Subject: [PATCH 11/19] Track Stream timestamps and add timeout to StreamFollower --- examples/stream_dump.cpp | 7 +- include/tins/packet.h | 10 +- include/tins/tcp_ip/flow.h | 298 +++++++++++++++++++++++++ include/tins/tcp_ip/stream.h | 279 ++++------------------- include/tins/tcp_ip/stream_follower.h | 37 +++- src/CMakeLists.txt | 1 + src/tcp_ip/flow.cpp | 308 ++++++++++++++++++++++++++ src/tcp_ip/stream.cpp | 264 ++-------------------- src/tcp_ip/stream_follower.cpp | 42 +++- tests/src/tcp_ip.cpp | 42 +++- 10 files changed, 789 insertions(+), 499 deletions(-) create mode 100644 include/tins/tcp_ip/flow.h create mode 100644 src/tcp_ip/flow.cpp diff --git a/examples/stream_dump.cpp b/examples/stream_dump.cpp index 66353ae..c1028f4 100644 --- a/examples/stream_dump.cpp +++ b/examples/stream_dump.cpp @@ -31,6 +31,7 @@ #include #include "tins/tcp_ip/stream_follower.h" #include "tins/sniffer.h" +#include "tins/packet.h" #include "tins/ip_address.h" #include "tins/ipv6_address.h" @@ -44,6 +45,7 @@ using std::exception; using Tins::Sniffer; using Tins::SnifferConfiguration; +using Tins::Packet; using Tins::TCPIP::StreamFollower; using Tins::TCPIP::Stream; @@ -148,7 +150,10 @@ int main(int argc, char* argv[]) { follower.new_stream_callback(&on_new_connection); // Now start capturing. Every time there's a new packet, call // follower.process_packet - sniffer.sniff_loop(bind(&StreamFollower::process_packet, &follower, _1)); + sniffer.sniff_loop([&](Packet& packet) { + follower.process_packet(packet); + return true; + }); } catch (exception& ex) { cerr << "Error: " << ex.what() << endl; diff --git a/include/tins/packet.h b/include/tins/packet.h index e1823a7..13450f7 100644 --- a/include/tins/packet.h +++ b/include/tins/packet.h @@ -148,11 +148,19 @@ public: /** * \brief Constructs a Packet from a PDU* and a Timestamp. * - * The PDU* is cloned using PDU::clone. + * The PDU is cloned using PDU::clone. */ Packet(const PDU* apdu, const Timestamp& tstamp) : pdu_(apdu->clone()), ts_(tstamp) { } + /** + * \brief Constructs a Packet from a PDU& and a Timestamp. + * + * The PDU is cloned using PDU::clone. + */ + Packet(const PDU& apdu, const Timestamp& tstamp) + : pdu_(apdu.clone()), ts_(tstamp) { } + /** * \brief Constructs a Packet from a PDU* and a Timestamp. * diff --git a/include/tins/tcp_ip/flow.h b/include/tins/tcp_ip/flow.h new file mode 100644 index 0000000..69b0858 --- /dev/null +++ b/include/tins/tcp_ip/flow.h @@ -0,0 +1,298 @@ +/* + * 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_FLOW_H +#define TINS_TCP_IP_FLOW_H + +#include "../cxxstd.h" + +// This classes use C++11 features +#if TINS_IS_CXX11 + +#include +#include +#include +#include +#include +#include "../macros.h" +#include "../hw_address.h" + +namespace Tins { + +class PDU; +class TCP; +class IPv4Address; +class IPv6Address; + +namespace TCPIP { + +/** + * \brief Represents an unidirectional TCP flow between 2 endpoints + * + * This class will keep the state for all the traffic sent by + * one of the peers in a TCP connection. This contains the sequence number, + * payload ready to be read and buffered payload, along with some other + * properties of the flow. + * + * A TCP stream (see class Stream) is made out of 2 Flows, so you should + * probably have a look at that class first. + * + * You shouldn't normally need to interact with this class. Stream already + * provides proxys to most of its Flow's attributes. + */ +class TINS_API Flow { +public: + /** + * \brief Enum that indicates the state of this flow. + * + * Note that although similar, this is not mapped to a TCP state-machine + * state. This is mostly used internally to know which packets the flow is + * expecting and to know when it's done sending data. + */ + enum State { + UNKNOWN, + SYN_SENT, + ESTABLISHED, + FIN_SENT, + RST_SENT + }; + + /** + * 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; + + /** + * The type used to store the callback called when new data is available + */ + typedef std::function data_available_callback_type; + + /** + * \brief The type used to store the callback called when data is buffered + * + * The arguments are the flow, the sequence number and payload that will + * be buffered. + */ + typedef std::function out_of_order_callback_type; + + /** + * Construct a Flow from an IPv4 address + * + * \param dst_address This flow's destination address + * \param dst_port This flow's destination port + * \param sequence_number The initial sequence number to be used + */ + Flow(const IPv4Address& dst_address, uint16_t dst_port, + uint32_t sequence_number); + + /** + * Construct a Flow from an IPv6 address + * + * \param dst_address This flow's destination address + * \param dst_port This flow's destination port + * \param sequence_number The initial sequence number to be used + */ + Flow(const IPv6Address& dst_address, uint16_t dst_port, + uint32_t sequence_number); + + /** + * \brief Sets the callback that will be executed when data is readable + * + * Whenever this flow has readable data, this callback will be executed. + * By readable, this means that there's non-out-of-order data captured. + * + * \param callback The callback to be executed + */ + void data_callback(const data_available_callback_type& callback); + + /** + * \brief Sets the callback that will be executed when out of order data arrives + * + * Whenever this flow receives out-of-order data, this callback will be + * executed. + * + * \param callback The callback to be executed + */ + void out_of_order_callback(const out_of_order_callback_type& callback); + + /** + * \brief Processes a packet. + * + * If this packet contains data and starts or overlaps with the current + * sequence number, then the data will be appended to this flow's payload + * and the data_callback will be executed. + * + * If this packet contains out-of-order data, it will be buffered and the + * buffering_callback will be executed. + * + * \param pdu The packet to be processed + * \sa Flow::data_callback + * \sa Flow::buffering_callback + */ + void process_packet(PDU& pdu); + + /** + * Indicates whether this flow uses IPv6 addresses + */ + bool is_v6() const; + + /** + * \brief Indicates whether this flow is finished + * + * A finished is considered to be finished if either it sent a + * packet with the FIN or RST flags on. + */ + bool is_finished() const; + + /** + * \brief Indicates whether a packet belongs to this flow + * + * Since Flow represents a unidirectional stream, this will only check + * the destination endpoint and not the source one. + * + * \param packet The packet to be checked + */ + bool packet_belongs(const PDU& packet) const; + + /** + * \brief Getter for the IPv4 destination address + * + * Note that it's only safe to execute this method if is_v6() == false + */ + IPv4Address dst_addr_v4() const; + + /** + * \brief Getter for the IPv6 destination address + * + * Note that it's only safe to execute this method if is_v6() == true + */ + IPv6Address dst_addr_v6() const; + + /** + * Getter for this flow's destination port + */ + uint16_t dport() const; + + /** + * Getter for this flow's payload (const) + */ + const payload_type& payload() const; + + /** + * Getter for this flow's destination port + */ + payload_type& payload(); + + /** + * Getter for this flow's state + */ + State state() const; + + /** + * Getter for this flow's sequence number + */ + uint32_t sequence_number() const; + + /** + * Getter for this flow's buffered payload (const) + */ + const buffered_payload_type& buffered_payload() const; + + /** + * Getter for this flow's buffered payload + */ + buffered_payload_type& buffered_payload(); + + /** + * Sets the state of this flow + * + * \param new_state The new state of this flow + */ + void state(State new_state); + + /** + * \brief Sets whether this flow should ignore data packets + * + * If the data packets are ignored then the flow will just be + * followed to keep track of its state. + */ + void ignore_data_packets(); + + /** + * \brief Returns the MSS for this Flow. + * + * If the MSS option wasn't provided by the peer, -1 is returned + */ + int mss() const; + + /** + * \brief Indicates whether this Flow supports selective acknowledgements + */ + bool sack_permitted() const; +private: + // Compress all flags into just one struct using bitfields + struct flags { + flags() : ignore_data_packets(0), sack_permitted(0) { + + } + + uint32_t is_v6:1, + ignore_data_packets:1, + sack_permitted: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); + + payload_type payload_; + buffered_payload_type buffered_payload_; + uint32_t seq_number_; + std::array dest_address_; + uint16_t dest_port_; + data_available_callback_type on_data_callback_; + out_of_order_callback_type on_out_of_order_callback_; + State state_; + int mss_; + flags flags_; +}; + +} // TCPIP +} // TINS + +#endif // TINS_IS_CXX11 +#endif // TINS_TCP_IP_FLOW_H + diff --git a/include/tins/tcp_ip/stream.h b/include/tins/tcp_ip/stream.h index 7495567..6e5f0b0 100644 --- a/include/tins/tcp_ip/stream.h +++ b/include/tins/tcp_ip/stream.h @@ -39,9 +39,11 @@ #include #include #include +#include #include #include "../macros.h" #include "../hw_address.h" +#include "flow.h" namespace Tins { @@ -52,238 +54,6 @@ class IPv6Address; namespace TCPIP { -/** - * \brief Represents an unidirectional TCP flow between 2 endpoints - * - * This class will keep the state for all the traffic sent by - * one of the peers in a TCP connection. This contains the sequence number, - * payload ready to be read and buffered payload, along with some other - * properties of the flow. - * - * A TCP stream (see class Stream) is made out of 2 Flows, so you should - * probably have a look at that class first. - * - * You shouldn't normally need to interact with this class. Stream already - * provides proxys to most of its Flow's attributes. - */ -class TINS_API Flow { -public: - /** - * \brief Enum that indicates the state of this flow. - * - * Note that although similar, this is not mapped to a TCP state-machine - * state. This is mostly used internally to know which packets the flow is - * expecting and to know when it's done sending data. - */ - enum State { - UNKNOWN, - SYN_SENT, - ESTABLISHED, - FIN_SENT, - RST_SENT - }; - - /** - * 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; - - /** - * The type used to store the callback called when new data is available - */ - typedef std::function data_available_callback_type; - - /** - * \brief The type used to store the callback called when data is buffered - * - * The arguments are the flow, the sequence number and payload that will - * be buffered. - */ - typedef std::function out_of_order_callback_type; - - /** - * Construct a Flow from an IPv4 address - * - * \param dst_address This flow's destination address - * \param dst_port This flow's destination port - * \param sequence_number The initial sequence number to be used - */ - Flow(const IPv4Address& dst_address, uint16_t dst_port, - uint32_t sequence_number); - - /** - * Construct a Flow from an IPv6 address - * - * \param dst_address This flow's destination address - * \param dst_port This flow's destination port - * \param sequence_number The initial sequence number to be used - */ - Flow(const IPv6Address& dst_address, uint16_t dst_port, - uint32_t sequence_number); - - /** - * \brief Sets the callback that will be executed when data is readable - * - * Whenever this flow has readable data, this callback will be executed. - * By readable, this means that there's non-out-of-order data captured. - * - * \param callback The callback to be executed - */ - void data_callback(const data_available_callback_type& callback); - - /** - * \brief Sets the callback that will be executed when out of order data arrives - * - * Whenever this flow receives out-of-order data, this callback will be - * executed. - * - * \param callback The callback to be executed - */ - void out_of_order_callback(const out_of_order_callback_type& callback); - - /** - * \brief Processes a packet. - * - * If this packet contains data and starts or overlaps with the current - * sequence number, then the data will be appended to this flow's payload - * and the data_callback will be executed. - * - * If this packet contains out-of-order data, it will be buffered and the - * buffering_callback will be executed. - * - * \param pdu The packet to be processed - * \sa Flow::data_callback - * \sa Flow::buffering_callback - */ - void process_packet(PDU& pdu); - - /** - * Indicates whether this flow uses IPv6 addresses - */ - bool is_v6() const; - - /** - * \brief Indicates whether this flow is finished - * - * A finished is considered to be finished if either it sent a - * packet with the FIN or RST flags on. - */ - bool is_finished() const; - - /** - * \brief Indicates whether a packet belongs to this flow - * - * Since Flow represents a unidirectional stream, this will only check - * the destination endpoint and not the source one. - * - * \param packet The packet to be checked - */ - bool packet_belongs(const PDU& packet) const; - - /** - * \brief Getter for the IPv4 destination address - * - * Note that it's only safe to execute this method if is_v6() == false - */ - IPv4Address dst_addr_v4() const; - - /** - * \brief Getter for the IPv6 destination address - * - * Note that it's only safe to execute this method if is_v6() == true - */ - IPv6Address dst_addr_v6() const; - - /** - * Getter for this flow's destination port - */ - uint16_t dport() const; - - /** - * Getter for this flow's payload (const) - */ - const payload_type& payload() const; - - /** - * Getter for this flow's destination port - */ - payload_type& payload(); - - /** - * Getter for this flow's state - */ - State state() const; - - /** - * Getter for this flow's sequence number - */ - uint32_t sequence_number() const; - - /** - * Getter for this flow's buffered payload (const) - */ - const buffered_payload_type& buffered_payload() const; - - /** - * Getter for this flow's buffered payload - */ - buffered_payload_type& buffered_payload(); - - /** - * Sets the state of this flow - * - * \param new_state The new state of this flow - */ - void state(State new_state); - - /** - * \brief Sets whether this flow should ignore data packets - * - * If the data packets are ignored then the flow will just be - * followed to keep track of its state. - */ - void ignore_data_packets(); - - /** - * \brief Returns the MSS for this Flow. - * - * If the MSS option wasn't provided by the peer, -1 is returned - */ - int mss() const; -private: - // Compress all flags into just one struct using bitfields - struct flags { - flags() : ignore_data_packets(0) { - - } - - uint32_t is_v6:1, - ignore_data_packets: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); - - payload_type payload_; - buffered_payload_type buffered_payload_; - uint32_t seq_number_; - std::array dest_address_; - uint16_t dest_port_; - data_available_callback_type on_data_callback_; - out_of_order_callback_type on_out_of_order_callback_; - State state_; - int mss_; - flags flags_; -}; - /** * \brief Represents a TCP stream * @@ -299,16 +69,21 @@ private: */ class TINS_API Stream { public: - /** - * The type used for callbacks - */ - typedef std::function stream_callback_type; - /** * The type used to store payloads */ typedef Flow::payload_type payload_type; + /** + * The type used to represent timestamps + */ + typedef std::chrono::microseconds timestamp_type; + + /** + * The type used for callbacks + */ + typedef std::function stream_callback_type; + /** * The type used for callbacks * @@ -326,14 +101,30 @@ public: /** * \brief Constructs a TCP stream using the provided packet. + * + * \param initial_packet The first packet of the stream + * \param ts The first packet's timestamp */ - Stream(PDU& initial_packet); + Stream(PDU& initial_packet, const timestamp_type& ts = timestamp_type()); /** * \brief Processes this packet. * * This will forward the packet appropriately to the client * or server flow. + * + * \param packet The packet to be processed + * \param ts The packet's timestamp + */ + void process_packet(PDU& packet, const timestamp_type& ts); + + /** + * \brief Processes this packet. + * + * This will forward the packet appropriately to the client + * or server flow. + * + * \param packet The packet to be processed */ void process_packet(PDU& packet); @@ -448,6 +239,16 @@ public: */ payload_type& server_payload(); + /** + * Getter for the creation time of this stream + */ + const timestamp_type& create_time() const; + + /** + * Getter for the last seen time of this stream + */ + const timestamp_type& last_seen() const; + /** * \brief Sets the callback to be executed when the stream is closed * @@ -555,6 +356,8 @@ private: out_of_order_callback_type on_server_out_of_order_callback_; hwaddress_type client_hw_addr_; hwaddress_type server_hw_addr_; + timestamp_type create_time_; + timestamp_type last_seen_; bool auto_cleanup_; }; diff --git a/include/tins/tcp_ip/stream_follower.h b/include/tins/tcp_ip/stream_follower.h index a277f43..5ac2209 100644 --- a/include/tins/tcp_ip/stream_follower.h +++ b/include/tins/tcp_ip/stream_follower.h @@ -44,6 +44,7 @@ class PDU; class TCP; class IPv4Address; class IPv6Address; +class Packet; namespace TCPIP { @@ -90,13 +91,20 @@ public: * and process it, or if it belongs to a new one, in which case it * starts tracking it. * - * This method always returns true so it can be easily plugged as - * the argument to Sniffer::sniff_loop. + * \param packet The packet to be processed + */ + void process_packet(PDU& packet); + + /** + * \brief Processes a packet + * + * This will detect if this packet belongs to an existing stream + * and process it, or if it belongs to a new one, in which case it + * starts tracking it. * * \param packet The packet to be processed - * \return Always true */ - bool process_packet(PDU& packet); + void process_packet(Packet& packet); /** * \brief Sets the callback to be executed when a new stream is captured. @@ -108,6 +116,17 @@ public: */ void new_stream_callback(const stream_callback_type& callback); + /** + * \brief Sets the maximum time a stream will be followed without capturing + * packets that belong to it. + * + * \param keep_alive The maximum time to keep unseen streams + */ + template + void stream_keep_alive(const std::chrono::duration& keep_alive) { + stream_keep_alive_ = keep_alive; + } + /** * Finds the stream identified by the provided arguments. * @@ -130,8 +149,12 @@ public: Stream& find_stream(IPv6Address client_addr, uint16_t client_port, IPv6Address server_addr, uint16_t server_port); private: - static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; typedef std::array address_type; + typedef Stream::timestamp_type timestamp_type; + + static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; + static const timestamp_type DEFAULT_CLEANUP_INTERVAL; + static const timestamp_type DEFAULT_KEEP_ALIVE; struct stream_id { stream_id(const address_type& client_addr, uint16_t client_port, @@ -153,10 +176,14 @@ private: Stream& find_stream(const stream_id& id); static address_type serialize(IPv4Address address); static address_type serialize(const IPv6Address& address); + void process_packet(PDU& packet, const timestamp_type& ts); + void cleanup_streams(const timestamp_type& now); streams_type streams_; stream_callback_type on_new_connection_; size_t max_buffered_chunks_; + timestamp_type last_cleanup_; + timestamp_type stream_keep_alive_; bool attach_to_flows_; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d436f0d..5a3da44 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,7 @@ ADD_LIBRARY( snap.cpp sniffer.cpp tcp.cpp + tcp_ip/flow.cpp tcp_ip/stream.cpp tcp_ip/stream_follower.cpp tcp_stream.cpp diff --git a/src/tcp_ip/flow.cpp b/src/tcp_ip/flow.cpp new file mode 100644 index 0000000..d425e29 --- /dev/null +++ b/src/tcp_ip/flow.cpp @@ -0,0 +1,308 @@ +/* + * 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/flow.h" + +#if TINS_IS_CXX11 + +#include +#include +#include "memory.h" +#include "ip_address.h" +#include "ipv6_address.h" +#include "tcp.h" +#include "ip.h" +#include "ipv6.h" +#include "rawpdu.h" +#include "exceptions.h" +#include "memory_helpers.h" + +using std::make_pair; +using std::bind; +using std::pair; +using std::runtime_error; +using std::numeric_limits; +using std::max; +using std::swap; + +using Tins::Memory::OutputMemoryStream; +using Tins::Memory::InputMemoryStream; + +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), state_(UNKNOWN), mss_(-1) { + OutputMemoryStream output(dest_address_.data(), dest_address_.size()); + output.write(dest_address); + flags_.is_v6 = false; +} + +Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, + uint32_t sequence_number) +: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), mss_(-1) { + OutputMemoryStream output(dest_address_.data(), dest_address_.size()); + output.write(dest_address); + flags_.is_v6 = true; +} + +void Flow::data_callback(const data_available_callback_type& callback) { + on_data_callback_ = callback; +} + +void Flow::out_of_order_callback(const out_of_order_callback_type& callback) { + on_out_of_order_callback_ = callback; +} + +void Flow::process_packet(PDU& pdu) { + TCP* tcp = pdu.find_pdu(); + RawPDU* raw = pdu.find_pdu(); + // If we sent a packet with RST or FIN on, this flow is done + if (tcp) { + update_state(*tcp); + } + if (flags_.ignore_data_packets) { + return; + } + if (!tcp || !raw) { + return; + } + const uint32_t chunk_end = tcp->seq() + raw->payload_size(); + // 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; + 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_) { + 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; + 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 we don't have any other payload, we're done + if (buffered_payload_.empty()) { + break; + } + } + } + if (added_some) { + if (on_data_callback_) { + on_data_callback_(*this); + } + } + } +} + +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()) { + buffered_payload_.insert(make_pair(seq, move(payload))); + } + else if (iter->second.size() < payload.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; + ++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; + } + else if ((tcp.flags() & TCP::RST) != 0) { + state_ = RST_SENT; + } + else if (state_ == SYN_SENT && (tcp.flags() & TCP::ACK) != 0) { + state_ = ESTABLISHED; + seq_number_++; + } + else if (state_ == UNKNOWN && (tcp.flags() & TCP::SYN) != 0) { + state_ = SYN_SENT; + seq_number_ = tcp.seq(); + const TCP::option* mss_option = tcp.search_option(TCP::MSS); + if (mss_option) { + mss_ = mss_option->to(); + } + flags_.sack_permitted = tcp.has_sack_permitted(); + } +} + +bool Flow::is_v6() const { + return flags_.is_v6; +} + +bool Flow::is_finished() const { + return state_ == FIN_SENT || state_ == RST_SENT; +} + +bool Flow::packet_belongs(const PDU& packet) const { + if (is_v6()) { + const IPv6* ip = packet.find_pdu(); + if (!ip || ip->dst_addr() != dst_addr_v6()) { + return false; + } + } + else { + const IP* ip = packet.find_pdu(); + if (!ip || ip->dst_addr() != dst_addr_v4()) { + return false; + } + } + const TCP* tcp = packet.find_pdu(); + return tcp && tcp->dport() == dport(); +} + +IPv4Address Flow::dst_addr_v4() const { + InputMemoryStream stream(dest_address_.data(), dest_address_.size()); + return stream.read(); +} + +IPv6Address Flow::dst_addr_v6() const { + InputMemoryStream stream(dest_address_.data(), dest_address_.size()); + return stream.read(); +} + +uint16_t Flow::dport() const { + return dest_port_; +} + +const Flow::payload_type& Flow::payload() const { + return payload_; +} + +Flow::State Flow::state() const { + return state_; +} + +uint32_t Flow::sequence_number() const { + return seq_number_; +} + +const Flow::buffered_payload_type& Flow::buffered_payload() const { + return buffered_payload_; +} + +Flow::buffered_payload_type& Flow::buffered_payload() { + return buffered_payload_; +} + +Flow::payload_type& Flow::payload() { + return payload_; +} + +void Flow::state(State new_state) { + state_ = new_state; +} + +void Flow::ignore_data_packets() { + flags_.ignore_data_packets = true; +} + +int Flow::mss() const { + return mss_; +} + +bool Flow::sack_permitted() const { + return flags_.sack_permitted; +} + +} // TCPIP +} // Tins + +#endif // TINS_IS_CXX11 diff --git a/src/tcp_ip/stream.cpp b/src/tcp_ip/stream.cpp index 93c109f..cc902f9 100644 --- a/src/tcp_ip/stream.cpp +++ b/src/tcp_ip/stream.cpp @@ -58,253 +58,10 @@ using Tins::Memory::InputMemoryStream; 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::Flow(const IPv4Address& dest_address, uint16_t dest_port, - uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), mss_(-1) { - OutputMemoryStream output(dest_address_.data(), dest_address_.size()); - output.write(dest_address); - flags_.is_v6 = false; -} - -Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, - uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), mss_(-1) { - OutputMemoryStream output(dest_address_.data(), dest_address_.size()); - output.write(dest_address); - flags_.is_v6 = true; -} - -void Flow::data_callback(const data_available_callback_type& callback) { - on_data_callback_ = callback; -} - -void Flow::out_of_order_callback(const out_of_order_callback_type& callback) { - on_out_of_order_callback_ = callback; -} - -void Flow::process_packet(PDU& pdu) { - TCP* tcp = pdu.find_pdu(); - RawPDU* raw = pdu.find_pdu(); - // If we sent a packet with RST or FIN on, this flow is done - if (tcp) { - update_state(*tcp); - } - if (flags_.ignore_data_packets) { - return; - } - if (!tcp || !raw) { - return; - } - const uint32_t chunk_end = tcp->seq() + raw->payload_size(); - // 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; - 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_) { - 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; - 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 we don't have any other payload, we're done - if (buffered_payload_.empty()) { - break; - } - } - } - if (added_some) { - if (on_data_callback_) { - on_data_callback_(*this); - } - } - } -} - -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()) { - buffered_payload_.insert(make_pair(seq, move(payload))); - } - else if (iter->second.size() < payload.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; - ++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; - } - else if ((tcp.flags() & TCP::RST) != 0) { - state_ = RST_SENT; - } - else if (state_ == SYN_SENT && (tcp.flags() & TCP::ACK) != 0) { - state_ = ESTABLISHED; - seq_number_++; - } - else if (state_ == UNKNOWN && (tcp.flags() & TCP::SYN) != 0) { - state_ = SYN_SENT; - seq_number_ = tcp.seq(); - const TCP::option* mss_option = tcp.search_option(TCP::MSS); - if (mss_option) { - mss_ = mss_option->to(); - } - } -} - -bool Flow::is_v6() const { - return flags_.is_v6; -} - -bool Flow::is_finished() const { - return state_ == FIN_SENT || state_ == RST_SENT; -} - -bool Flow::packet_belongs(const PDU& packet) const { - if (is_v6()) { - const IPv6* ip = packet.find_pdu(); - if (!ip || ip->dst_addr() != dst_addr_v6()) { - return false; - } - } - else { - const IP* ip = packet.find_pdu(); - if (!ip || ip->dst_addr() != dst_addr_v4()) { - return false; - } - } - const TCP* tcp = packet.find_pdu(); - return tcp && tcp->dport() == dport(); -} - -IPv4Address Flow::dst_addr_v4() const { - InputMemoryStream stream(dest_address_.data(), dest_address_.size()); - return stream.read(); -} - -IPv6Address Flow::dst_addr_v6() const { - InputMemoryStream stream(dest_address_.data(), dest_address_.size()); - return stream.read(); -} - -uint16_t Flow::dport() const { - return dest_port_; -} - -const Flow::payload_type& Flow::payload() const { - return payload_; -} - -Flow::State Flow::state() const { - return state_; -} - -uint32_t Flow::sequence_number() const { - return seq_number_; -} - -const Flow::buffered_payload_type& Flow::buffered_payload() const { - return buffered_payload_; -} - -Flow::buffered_payload_type& Flow::buffered_payload() { - return buffered_payload_; -} - -Flow::payload_type& Flow::payload() { - return payload_; -} - -void Flow::state(State new_state) { - state_ = new_state; -} - -void Flow::ignore_data_packets() { - flags_.ignore_data_packets = true; -} - -int Flow::mss() const { - return mss_; -} - -// Stream - -Stream::Stream(PDU& packet) +Stream::Stream(PDU& packet, const timestamp_type& ts) : client_flow_(extract_client_flow(packet)), - server_flow_(extract_server_flow(packet)), auto_cleanup_(true) { + server_flow_(extract_server_flow(packet)), create_time_(ts), + last_seen_(ts), auto_cleanup_(true) { // Update client flow state client_flow().process_packet(packet); const EthernetII* eth = packet.find_pdu(); @@ -314,7 +71,8 @@ Stream::Stream(PDU& packet) } } -void Stream::process_packet(PDU& packet) { +void Stream::process_packet(PDU& packet, const timestamp_type& ts) { + last_seen_ = ts; if (client_flow_.packet_belongs(packet)) { client_flow_.process_packet(packet); } @@ -326,6 +84,10 @@ void Stream::process_packet(PDU& packet) { } } +void Stream::process_packet(PDU& packet) { + return process_packet(packet, timestamp_type(0)); +} + Flow& Stream::client_flow() { return client_flow_; } @@ -435,6 +197,14 @@ Stream::payload_type& Stream::server_payload() { return server_flow().payload(); } +const Stream::timestamp_type& Stream::create_time() const { + return create_time_; +} + +const Stream::timestamp_type& Stream::last_seen() const { + return last_seen_; +} + Flow Stream::extract_client_flow(const PDU& packet) { const TCP* tcp = packet.find_pdu(); if (!tcp) { diff --git a/src/tcp_ip/stream_follower.cpp b/src/tcp_ip/stream_follower.cpp index b7b2404..3f643aa 100644 --- a/src/tcp_ip/stream_follower.cpp +++ b/src/tcp_ip/stream_follower.cpp @@ -40,6 +40,7 @@ #include "ip.h" #include "ipv6.h" #include "rawpdu.h" +#include "packet.h" #include "exceptions.h" #include "memory_helpers.h" @@ -50,6 +51,9 @@ using std::runtime_error; using std::numeric_limits; using std::max; using std::swap; +using std::chrono::system_clock; +using std::chrono::minutes; +using std::chrono::duration_cast; using Tins::Memory::OutputMemoryStream; using Tins::Memory::InputMemoryStream; @@ -58,13 +62,25 @@ namespace Tins { namespace TCPIP { const size_t StreamFollower::DEFAULT_MAX_BUFFERED_CHUNKS = 512; +const StreamFollower::timestamp_type StreamFollower::DEFAULT_KEEP_ALIVE = minutes(5); StreamFollower::StreamFollower() -: max_buffered_chunks_(DEFAULT_MAX_BUFFERED_CHUNKS), attach_to_flows_(false) { +: max_buffered_chunks_(DEFAULT_MAX_BUFFERED_CHUNKS), last_cleanup_(0), + stream_keep_alive_(DEFAULT_KEEP_ALIVE), attach_to_flows_(false) { } -bool StreamFollower::process_packet(PDU& packet) { +void StreamFollower::process_packet(PDU& packet) { + // Use current time + const system_clock::duration ts = system_clock::now().time_since_epoch(); + process_packet(packet, duration_cast(ts)); +} + +void StreamFollower::process_packet(Packet& packet) { + process_packet(*packet.pdu(), packet.timestamp()); +} + +void StreamFollower::process_packet(PDU& packet, const timestamp_type& ts) { stream_id identifier = make_stream_id(packet); streams_type::iterator iter = streams_.find(identifier); bool process = true; @@ -73,7 +89,7 @@ bool StreamFollower::process_packet(PDU& packet) { // Start tracking if they're either SYNs or they contain data (attach // to an already running flow). if (tcp.flags() == TCP::SYN || (attach_to_flows_ && tcp.find_pdu() != 0)) { - iter = streams_.insert(make_pair(identifier, Stream(packet))).first; + iter = streams_.insert(make_pair(identifier, Stream(packet, ts))).first; iter->second.setup_flows_callbacks(); if (on_new_connection_) { on_new_connection_(iter->second); @@ -99,14 +115,16 @@ bool StreamFollower::process_packet(PDU& packet) { // it and it contains payload if (process) { Stream& stream = iter->second; - stream.process_packet(packet); + stream.process_packet(packet, ts); size_t total_chunks = stream.client_flow().buffered_payload().size() + stream.server_flow().buffered_payload().size(); if (stream.is_finished() || total_chunks > max_buffered_chunks_) { streams_.erase(iter); } } - return true; + if (last_cleanup_ + stream_keep_alive_ <= ts) { + cleanup_streams(ts); + } } void StreamFollower::new_stream_callback(const stream_callback_type& callback) { @@ -173,6 +191,20 @@ StreamFollower::address_type StreamFollower::serialize(const IPv6Address& addres return addr; } +void StreamFollower::cleanup_streams(const timestamp_type& now) { + streams_type::iterator iter = streams_.begin(); + while (iter != streams_.end()) { + if (iter->second.last_seen() + stream_keep_alive_ <= now) { + // TODO: execute some callback here + streams_.erase(iter++); + } + else { + ++iter; + } + } + last_cleanup_ = now; +} + // stream_id StreamFollower::stream_id::stream_id(const address_type& client_addr, diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index 616649e..cb8581a 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -16,9 +16,11 @@ #include "exceptions.h" #include "ethernetII.h" #include "rawpdu.h" +#include "packet.h" #include "utils.h" using namespace std; +using namespace std::chrono; using namespace Tins; using namespace Tins::TCPIP; @@ -331,8 +333,15 @@ TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { packets[0].dst_addr("05:04:03:02:01:00"); StreamFollower follower; follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); + + Stream::timestamp_type ts(10000); + Stream::timestamp_type create_time = ts; for (size_t i = 0; i < packets.size(); ++i) { - follower.process_packet(packets[i]); + if (i != 0) { + ts += milliseconds(100); + } + Packet packet(packets[i], ts); + follower.process_packet(packet); } Stream& stream = follower.find_stream(IPv4Address("1.2.3.4"), 22, IPv4Address("4.3.2.1"), 25); @@ -350,6 +359,8 @@ TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { EXPECT_EQ(HWAddress<6>("05:04:03:02:01:00"), stream.server_hw_addr()); EXPECT_EQ(22, stream.client_port()); EXPECT_EQ(25, stream.server_port()); + EXPECT_EQ(create_time, stream.create_time()); + EXPECT_EQ(ts, stream.last_seen()); IP server_packet = IP("1.2.3.4", "4.3.2.1") / TCP(22, 25); server_packet.rfind_pdu().flags(TCP::ACK); @@ -359,7 +370,7 @@ TEST_F(FlowTest, StreamFollower_ThreeWayHandshake) { EXPECT_EQ(61, stream.server_flow().sequence_number()); } -TEST_F(FlowTest, StreamFollower_MSS) { +TEST_F(FlowTest, StreamFollower_TCPOptions) { using std::placeholders::_1; vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); @@ -367,6 +378,8 @@ TEST_F(FlowTest, StreamFollower_MSS) { packets[0].rfind_pdu().mss(1220); // Server's mss is 1460 packets[1].rfind_pdu().mss(1460); + // Server supports SACK + packets[1].rfind_pdu().sack_permitted(); StreamFollower follower; follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); for (size_t i = 0; i < packets.size(); ++i) { @@ -376,8 +389,33 @@ TEST_F(FlowTest, StreamFollower_MSS) { IPv4Address("4.3.2.1"), 25); EXPECT_EQ(1220, stream.client_flow().mss()); EXPECT_EQ(1460, stream.server_flow().mss()); + EXPECT_FALSE(stream.client_flow().sack_permitted()); + EXPECT_TRUE(stream.server_flow().sack_permitted()); } +TEST_F(FlowTest, StreamFollower_CleanupWorks) { + using std::placeholders::_1; + + vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); + StreamFollower follower; + follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); + packets[2].rfind_pdu().src_addr("6.6.6.6"); + auto base_time = duration_cast(system_clock::now().time_since_epoch()); + Packet packet1(packets[0], base_time); + Packet packet2(packets[1], base_time + seconds(50)); + Packet packet3(packets[2], base_time + minutes(10)); + follower.process_packet(packet1); + Stream& stream = follower.find_stream(IPv4Address("1.2.3.4"), 22, + IPv4Address("4.3.2.1"), 25); + EXPECT_EQ(base_time, stream.create_time()); + follower.process_packet(packet2); + follower.process_packet(packet3); + // At this point, it should be cleaned up + EXPECT_THROW( + follower.find_stream(IPv4Address("1.2.3.4"), 22, IPv4Address("4.3.2.1"), 25), + stream_not_found + ); +} TEST_F(FlowTest, StreamFollower_RSTClosesStream) { using std::placeholders::_1; From 48c068b84ac106d8a2c9b7e7cac712921bcfff7a Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sat, 13 Feb 2016 11:23:08 -0800 Subject: [PATCH 12/19] Add callbacks for stream termination events --- include/tins/tcp_ip/flow.h | 31 +++++++++++++-------- include/tins/tcp_ip/stream.h | 12 ++++---- include/tins/tcp_ip/stream_follower.h | 40 +++++++++++++++++++++++---- src/tcp_ip/flow.cpp | 30 ++++++++++++++------ src/tcp_ip/stream.cpp | 4 +-- src/tcp_ip/stream_follower.cpp | 31 ++++++++++++++++----- tests/src/tcp_ip.cpp | 9 +++++- 7 files changed, 116 insertions(+), 41 deletions(-) diff --git a/include/tins/tcp_ip/flow.h b/include/tins/tcp_ip/flow.h index 69b0858..d48a1af 100644 --- a/include/tins/tcp_ip/flow.h +++ b/include/tins/tcp_ip/flow.h @@ -106,7 +106,7 @@ public: */ typedef std::function out_of_order_callback_type; + const payload_type&)> flow_packet_callback_type; /** * Construct a Flow from an IPv4 address @@ -146,7 +146,7 @@ public: * * \param callback The callback to be executed */ - void out_of_order_callback(const out_of_order_callback_type& callback); + void out_of_order_callback(const flow_packet_callback_type& callback); /** * \brief Processes a packet. @@ -188,54 +188,59 @@ public: bool packet_belongs(const PDU& packet) const; /** - * \brief Getter for the IPv4 destination address + * \brief Retrieves the IPv4 destination address * * Note that it's only safe to execute this method if is_v6() == false */ IPv4Address dst_addr_v4() const; /** - * \brief Getter for the IPv6 destination address + * \brief Retrieves the IPv6 destination address * * Note that it's only safe to execute this method if is_v6() == true */ IPv6Address dst_addr_v6() const; /** - * Getter for this flow's destination port + * Retrieves this flow's destination port */ uint16_t dport() const; /** - * Getter for this flow's payload (const) + * Retrieves this flow's payload (const) */ const payload_type& payload() const; /** - * Getter for this flow's destination port + * Retrieves this flow's destination port */ payload_type& payload(); /** - * Getter for this flow's state + * Retrieves this flow's state */ State state() const; /** - * Getter for this flow's sequence number + * Retrieves this flow's sequence number */ uint32_t sequence_number() const; /** - * Getter for this flow's buffered payload (const) + * Retrieves this flow's buffered payload (const) */ const buffered_payload_type& buffered_payload() const; /** - * Getter for this flow's buffered payload + * Retrieves this flow's buffered payload */ buffered_payload_type& buffered_payload(); + /** + * Retrieves this flow's total buffered bytes + */ + uint32_t total_buffered_bytes() const; + /** * Sets the state of this flow * @@ -277,14 +282,16 @@ private: 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_; std::array dest_address_; uint16_t dest_port_; data_available_callback_type on_data_callback_; - out_of_order_callback_type on_out_of_order_callback_; + flow_packet_callback_type on_out_of_order_callback_; State state_; int mss_; flags flags_; diff --git a/include/tins/tcp_ip/stream.h b/include/tins/tcp_ip/stream.h index 6e5f0b0..87256ee 100644 --- a/include/tins/tcp_ip/stream.h +++ b/include/tins/tcp_ip/stream.h @@ -85,13 +85,13 @@ public: typedef std::function stream_callback_type; /** - * The type used for callbacks + * The type used for packet-triggered callbacks * * /sa Flow::buffering_callback */ typedef std::function out_of_order_callback_type; + const payload_type&)> stream_packet_callback_type; /** * The type used to store hardware addresses @@ -279,7 +279,7 @@ public: * \sa Flow::buffering_callback * \param callback The callback to be set */ - void client_out_of_order_callback(const out_of_order_callback_type& callback); + void client_out_of_order_callback(const stream_packet_callback_type& callback); /** * \brief Sets the callback to be executed when there's new buffered @@ -288,7 +288,7 @@ public: * \sa Flow::buffering_callback * \param callback The callback to be set */ - void server_out_of_order_callback(const out_of_order_callback_type& callback); + void server_out_of_order_callback(const stream_packet_callback_type& callback); /** * \brief Indicates that the data packets sent by the client should be @@ -352,8 +352,8 @@ private: stream_callback_type on_stream_closed_; stream_callback_type on_client_data_callback_; stream_callback_type on_server_data_callback_; - out_of_order_callback_type on_client_out_of_order_callback_; - out_of_order_callback_type on_server_out_of_order_callback_; + stream_packet_callback_type on_client_out_of_order_callback_; + stream_packet_callback_type on_server_out_of_order_callback_; hwaddress_type client_hw_addr_; hwaddress_type server_hw_addr_; timestamp_type create_time_; diff --git a/include/tins/tcp_ip/stream_follower.h b/include/tins/tcp_ip/stream_follower.h index 5ac2209..da6732f 100644 --- a/include/tins/tcp_ip/stream_follower.h +++ b/include/tins/tcp_ip/stream_follower.h @@ -79,6 +79,21 @@ public: */ typedef Stream::stream_callback_type stream_callback_type; + /** + * Enum to indicate the reason why a stream was terminated + */ + enum TerminationReason { + TIMEOUT, ///< The stream was terminated due to a timeout + BUFFERED_DATA ///< The stream was terminated because it had too much buffered data + }; + + /** + * \brief The type used for stream termination callbacks + * + * \sa StreamFollower::stream_termination_callback + */ + typedef std::function stream_termination_callback_type; + /** * Default constructor */ @@ -116,6 +131,19 @@ public: */ void new_stream_callback(const stream_callback_type& callback); + /** + * \brief Sets the stream termination callback + * + * A stream is terminated when either: + * + * * It contains too much buffered data. + * * No packets have been seen for some time interval. + * + * \param callback The callback to be executed on stream termination + * \sa StreamFollower::stream_keep_alive + */ + void stream_termination_callback(const stream_termination_callback_type& callback); + /** * \brief Sets the maximum time a stream will be followed without capturing * packets that belong to it. @@ -135,8 +163,8 @@ public: * \param server_addr The server's address * \param server_addr The server's port */ - Stream& find_stream(IPv4Address client_addr, uint16_t client_port, - IPv4Address server_addr, uint16_t server_port); + Stream& find_stream(const IPv4Address& client_addr, uint16_t client_port, + const IPv4Address& server_addr, uint16_t server_port); /** * Finds the stream identified by the provided arguments. @@ -146,14 +174,14 @@ public: * \param server_addr The server's address * \param server_addr The server's port */ - Stream& find_stream(IPv6Address client_addr, uint16_t client_port, - IPv6Address server_addr, uint16_t server_port); + Stream& find_stream(const IPv6Address& client_addr, uint16_t client_port, + const IPv6Address& server_addr, uint16_t server_port); private: typedef std::array address_type; typedef Stream::timestamp_type timestamp_type; static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; - static const timestamp_type DEFAULT_CLEANUP_INTERVAL; + static const uint32_t DEFAULT_MAX_BUFFERED_BYTES; static const timestamp_type DEFAULT_KEEP_ALIVE; struct stream_id { @@ -181,7 +209,9 @@ private: streams_type streams_; stream_callback_type on_new_connection_; + stream_termination_callback_type on_stream_termination_; size_t max_buffered_chunks_; + uint32_t max_buffered_bytes_; timestamp_type last_cleanup_; timestamp_type stream_keep_alive_; bool attach_to_flows_; diff --git a/src/tcp_ip/flow.cpp b/src/tcp_ip/flow.cpp index d425e29..52f2df9 100644 --- a/src/tcp_ip/flow.cpp +++ b/src/tcp_ip/flow.cpp @@ -75,32 +75,40 @@ int seq_compare(uint32_t seq1, uint32_t seq2) { Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), mss_(-1) { +: seq_number_(sequence_number), dest_port_(dest_port) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); flags_.is_v6 = false; + initialize(); } Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port, uint32_t sequence_number) -: seq_number_(sequence_number), dest_port_(dest_port), state_(UNKNOWN), mss_(-1) { +: seq_number_(sequence_number), dest_port_(dest_port) { OutputMemoryStream output(dest_address_.data(), dest_address_.size()); output.write(dest_address); flags_.is_v6 = true; + initialize(); +} + +void Flow::initialize() { + total_buffered_bytes_ = 0; + state_ = UNKNOWN; + mss_ = -1; } void Flow::data_callback(const data_available_callback_type& callback) { on_data_callback_ = callback; } -void Flow::out_of_order_callback(const out_of_order_callback_type& callback) { +void Flow::out_of_order_callback(const flow_packet_callback_type& callback) { on_out_of_order_callback_ = callback; } void Flow::process_packet(PDU& pdu) { TCP* tcp = pdu.find_pdu(); RawPDU* raw = pdu.find_pdu(); - // If we sent a packet with RST or FIN on, this flow is done + // Update the internal state first if (tcp) { update_state(*tcp); } @@ -142,6 +150,8 @@ void Flow::process_packet(PDU& pdu) { 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) @@ -164,10 +174,6 @@ void Flow::process_packet(PDU& pdu) { seq_number_ += iter->second.size(); iter = erase_iterator(iter); added_some = true; - // If we don't have any other payload, we're done - if (buffered_payload_.empty()) { - break; - } } } if (added_some) { @@ -182,9 +188,12 @@ 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); @@ -193,6 +202,7 @@ void Flow::store_payload(uint32_t seq, payload_type 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()) { @@ -282,6 +292,10 @@ Flow::buffered_payload_type& Flow::buffered_payload() { return buffered_payload_; } +uint32_t Flow::total_buffered_bytes() const { + return total_buffered_bytes_; +} + Flow::payload_type& Flow::payload() { return payload_; } diff --git a/src/tcp_ip/stream.cpp b/src/tcp_ip/stream.cpp index cc902f9..1acc742 100644 --- a/src/tcp_ip/stream.cpp +++ b/src/tcp_ip/stream.cpp @@ -116,11 +116,11 @@ void Stream::server_data_callback(const stream_callback_type& callback) { on_server_data_callback_ = callback; } -void Stream::client_out_of_order_callback(const out_of_order_callback_type& callback) { +void Stream::client_out_of_order_callback(const stream_packet_callback_type& callback) { on_client_out_of_order_callback_ = callback; } -void Stream::server_out_of_order_callback(const out_of_order_callback_type& callback) { +void Stream::server_out_of_order_callback(const stream_packet_callback_type& callback) { on_server_out_of_order_callback_ = callback; } diff --git a/src/tcp_ip/stream_follower.cpp b/src/tcp_ip/stream_follower.cpp index 3f643aa..54ea341 100644 --- a/src/tcp_ip/stream_follower.cpp +++ b/src/tcp_ip/stream_follower.cpp @@ -62,10 +62,12 @@ namespace Tins { namespace TCPIP { const size_t StreamFollower::DEFAULT_MAX_BUFFERED_CHUNKS = 512; +const uint32_t StreamFollower::DEFAULT_MAX_BUFFERED_BYTES = 3 * 1024 * 1024; // 3MB const StreamFollower::timestamp_type StreamFollower::DEFAULT_KEEP_ALIVE = minutes(5); StreamFollower::StreamFollower() -: max_buffered_chunks_(DEFAULT_MAX_BUFFERED_CHUNKS), last_cleanup_(0), +: max_buffered_chunks_(DEFAULT_MAX_BUFFERED_CHUNKS), + max_buffered_bytes_(DEFAULT_MAX_BUFFERED_BYTES), last_cleanup_(0), stream_keep_alive_(DEFAULT_KEEP_ALIVE), attach_to_flows_(false) { } @@ -118,7 +120,15 @@ void StreamFollower::process_packet(PDU& packet, const timestamp_type& ts) { stream.process_packet(packet, ts); size_t total_chunks = stream.client_flow().buffered_payload().size() + stream.server_flow().buffered_payload().size(); - if (stream.is_finished() || total_chunks > max_buffered_chunks_) { + uint32_t total_buffered_bytes = stream.client_flow().total_buffered_bytes() + + stream.server_flow().total_buffered_bytes(); + bool terminate_stream = total_chunks > max_buffered_chunks_ || + total_buffered_bytes > max_buffered_bytes_; + if (stream.is_finished() || terminate_stream) { + // If we're terminating the stream, execute the termination callback + if (terminate_stream && on_stream_termination_) { + on_stream_termination_(stream, BUFFERED_DATA); + } streams_.erase(iter); } } @@ -131,15 +141,19 @@ void StreamFollower::new_stream_callback(const stream_callback_type& callback) { on_new_connection_ = callback; } -Stream& StreamFollower::find_stream(IPv4Address client_addr, uint16_t client_port, - IPv4Address server_addr, uint16_t server_port) { +void StreamFollower::stream_termination_callback(const stream_termination_callback_type& callback) { + on_stream_termination_ = callback; +} + +Stream& StreamFollower::find_stream(const IPv4Address& client_addr, uint16_t client_port, + const IPv4Address& server_addr, uint16_t server_port) { stream_id identifier(serialize(client_addr), client_port, serialize(server_addr), server_port); return find_stream(identifier); } -Stream& StreamFollower::find_stream(IPv6Address client_addr, uint16_t client_port, - IPv6Address server_addr, uint16_t server_port) { +Stream& StreamFollower::find_stream(const IPv6Address& client_addr, uint16_t client_port, + const IPv6Address& server_addr, uint16_t server_port) { stream_id identifier(serialize(client_addr), client_port, serialize(server_addr), server_port); return find_stream(identifier); @@ -195,7 +209,10 @@ void StreamFollower::cleanup_streams(const timestamp_type& now) { streams_type::iterator iter = streams_.begin(); while (iter != streams_.end()) { if (iter->second.last_seen() + stream_keep_alive_ <= now) { - // TODO: execute some callback here + // If we have a termination callback, execute it + if (on_stream_termination_) { + on_stream_termination_(iter->second, TIMEOUT); + } streams_.erase(iter++); } else { diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index cb8581a..f1fc76a 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -147,6 +147,8 @@ void FlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks, } string flow_payload = merge_chunks(flow_payload_chunks); EXPECT_EQ(payload, string(flow_payload.begin(), flow_payload.end())); + EXPECT_EQ(0, flow.total_buffered_bytes()); + EXPECT_TRUE(flow.buffered_payload().empty()); } void FlowTest::run_test(uint32_t initial_seq, const ordering_info_type& chunks) { @@ -396,9 +398,13 @@ TEST_F(FlowTest, StreamFollower_TCPOptions) { TEST_F(FlowTest, StreamFollower_CleanupWorks) { using std::placeholders::_1; + bool timed_out = false; vector packets = three_way_handshake(29, 60, "1.2.3.4", 22, "4.3.2.1", 25); StreamFollower follower; follower.new_stream_callback(bind(&FlowTest::on_new_stream, this, _1)); + follower.stream_termination_callback([&](Stream&, StreamFollower::TerminationReason reason) { + timed_out = (reason == StreamFollower::TIMEOUT); + }); packets[2].rfind_pdu().src_addr("6.6.6.6"); auto base_time = duration_cast(system_clock::now().time_since_epoch()); Packet packet1(packets[0], base_time); @@ -414,7 +420,8 @@ TEST_F(FlowTest, StreamFollower_CleanupWorks) { EXPECT_THROW( follower.find_stream(IPv4Address("1.2.3.4"), 22, IPv4Address("4.3.2.1"), 25), stream_not_found - ); + ); + EXPECT_TRUE(timed_out); } TEST_F(FlowTest, StreamFollower_RSTClosesStream) { From 116eb9f1c18ee43a20a90fd1ecda506be7f62e53 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sat, 13 Feb 2016 20:24:15 -0800 Subject: [PATCH 13/19] 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 From f8445c2e5c5c48510445b9f12f7c0dc7e102c43e Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sat, 13 Feb 2016 20:34:39 -0800 Subject: [PATCH 14/19] Fix travis build script --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7a8e7d5..0741854 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,9 +23,5 @@ 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 From 2498ebf7d6ab19fb7b82643b7ef78b9bf1d196be Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sat, 13 Feb 2016 21:26:46 -0800 Subject: [PATCH 15/19] Fix ACK tracker tests build --- tests/src/tcp_ip.cpp | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index 4b59c9c..a0342f4 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -515,6 +515,7 @@ TEST_F(FlowTest, StreamFollower_FollowStream) { #ifdef HAVE_ACK_TRACKER +using namespace boost; using namespace boost::icl; class AckTrackerTest : public testing::Test { @@ -550,32 +551,30 @@ TCP make_tcp_ack(uint32_t ack_number, SackEdges&&... rest) { 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; -} - +// This section compares ranges using +// +// EXPECT_TRUE(r1 == r2) +// +// Since otherwise gtest fails to compile when trying to print an interval +// to a std::ostream TEST_F(AckTrackerTest, AckedRange_1) { AckedRange range(0, 100); EXPECT_TRUE(range.has_next()); - EXPECT_EQ(interval_type::closed(0, 100), range.next()); + EXPECT_TRUE(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_TRUE(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_TRUE(interval_type::right_open(0, 1) == range.next()); EXPECT_FALSE(range.has_next()); } @@ -583,7 +582,7 @@ 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_TRUE(interval_type::left_open(maximum - 1, maximum) == range.next()); EXPECT_FALSE(range.has_next()); } @@ -591,12 +590,12 @@ 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()), + EXPECT_TRUE( + interval_type::closed(first, numeric_limits::max()) == range.next() ); EXPECT_TRUE(range.has_next()); - EXPECT_EQ(interval_type::closed(0, 100), range.next()); + EXPECT_TRUE(interval_type::closed(0, 100) == range.next()); EXPECT_FALSE(range.has_next()); } From abe94ece524d43d69d05ba80da4b93501b192df5 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sat, 13 Feb 2016 22:45:11 -0800 Subject: [PATCH 16/19] Allow asking whether segment was acked --- include/tins/tcp_ip/ack_tracker.h | 8 ++++++++ include/tins/tcp_ip/flow.h | 12 ++++++++++++ src/tcp_ip/ack_tracker.cpp | 28 ++++++++++++++++++++++++++++ src/tcp_ip/flow.cpp | 11 +++++++++++ tests/src/tcp_ip.cpp | 14 +++++++++++++- 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/include/tins/tcp_ip/ack_tracker.h b/include/tins/tcp_ip/ack_tracker.h index ae5d873..4ff8c25 100644 --- a/include/tins/tcp_ip/ack_tracker.h +++ b/include/tins/tcp_ip/ack_tracker.h @@ -129,6 +129,14 @@ public: * \brief Retrieves all acked intervals by Selective ACKs */ const interval_set_type& acked_intervals() const; + + /** + * \brief Indicates whether the given segment has been already ACKed + * + * \param sequence_number The segment's sequence number + * \param length The segment's length + */ + bool is_segment_acked(uint32_t sequence_number, uint32_t length) const; private: void process_sack(const std::vector& sack); void cleanup_sacked_intervals(uint32_t old_ack, uint32_t new_ack); diff --git a/include/tins/tcp_ip/flow.h b/include/tins/tcp_ip/flow.h index 945ddf4..8b2845e 100644 --- a/include/tins/tcp_ip/flow.h +++ b/include/tins/tcp_ip/flow.h @@ -282,6 +282,18 @@ public: * \brief Indicates whether ACK number tracking is enabled */ bool ack_tracking_enabled() const; + + #ifdef HAVE_ACK_TRACKER + /** + * Retrieves the ACK tracker for this Flow (const) + */ + const AckTracker& ack_tracker() const; + + /** + * Retrieves the ACK tracker for this Flow + */ + AckTracker& ack_tracker(); + #endif // HAVE_ACK_TRACKER private: // Compress all flags into just one struct using bitfields struct flags { diff --git a/src/tcp_ip/ack_tracker.cpp b/src/tcp_ip/ack_tracker.cpp index 9cdd9ab..ba2c0bd 100644 --- a/src/tcp_ip/ack_tracker.cpp +++ b/src/tcp_ip/ack_tracker.cpp @@ -38,11 +38,23 @@ using std::vector; using std::numeric_limits; +using boost::icl::interval_bounds; +using boost::icl::contains; + using Tins::Internals::seq_compare; namespace Tins { namespace TCPIP { +uint32_t interval_end(const AckedRange::interval_type& interval) { + if (interval.bounds() == interval_bounds::right_open()) { + return interval.upper() - 1; + } + else { + return interval.upper(); + } +} + // AckedRange AckedRange::AckedRange(uint32_t first, uint32_t last) @@ -140,6 +152,22 @@ const AckTracker::interval_set_type& AckTracker::acked_intervals() const { return acked_intervals_; } +bool AckTracker::is_segment_acked(uint32_t sequence_number, uint32_t length) const { + if (length == 0) { + return true; + } + AckedRange range(sequence_number, sequence_number + length - 1); + while (range.has_next()) { + AckedRange::interval_type interval = range.next(); + const int comparison = seq_compare(interval_end(interval), ack_number_); + // Only check for SACKed intervals if the segment finishes after our ACK number + if (comparison >= 0 && !contains(acked_intervals_, interval)) { + return false; + } + } + return true; +} + } // TCPIP } // Tins diff --git a/src/tcp_ip/flow.cpp b/src/tcp_ip/flow.cpp index 15f79b5..01d8f81 100644 --- a/src/tcp_ip/flow.cpp +++ b/src/tcp_ip/flow.cpp @@ -319,6 +319,17 @@ bool Flow::ack_tracking_enabled() const { return flags_.ack_tracking; } +#ifdef HAVE_ACK_TRACKER +const AckTracker& Flow::ack_tracker() const { + return ack_tracker_; +} + +AckTracker& Flow::ack_tracker() { + return ack_tracker_; +} + +#endif // HAVE_ACK_TRACKER + } // TCPIP } // Tins diff --git a/tests/src/tcp_ip.cpp b/tests/src/tcp_ip.cpp index a0342f4..5863fb3 100644 --- a/tests/src/tcp_ip.cpp +++ b/tests/src/tcp_ip.cpp @@ -601,9 +601,14 @@ TEST_F(AckTrackerTest, AckedRange_WrapAround) { TEST_F(AckTrackerTest, AckingTcp1) { AckTracker tracker(0, false); - EXPECT_EQ(0, tracker.ack_number()); + EXPECT_EQ(0, tracker.ack_number()); tracker.process_packet(make_tcp_ack(100)); EXPECT_EQ(100, tracker.ack_number()); + EXPECT_TRUE(tracker.is_segment_acked(0, 10)); + EXPECT_TRUE(tracker.is_segment_acked(50, 10)); + EXPECT_TRUE(tracker.is_segment_acked(99, 1)); + EXPECT_FALSE(tracker.is_segment_acked(90, 20)); + EXPECT_FALSE(tracker.is_segment_acked(99, 2)); tracker.process_packet(make_tcp_ack(50)); EXPECT_EQ(100, tracker.ack_number()); tracker.process_packet(make_tcp_ack(150)); @@ -635,6 +640,9 @@ 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()); + EXPECT_TRUE(tracker.is_segment_acked(2, 3)); + EXPECT_TRUE(tracker.is_segment_acked(9, 2)); + EXPECT_FALSE(tracker.is_segment_acked(2, 9)); tracker.process_packet(make_tcp_ack(9)); EXPECT_EQ(1, tracker.acked_intervals().size()); @@ -652,6 +660,10 @@ TEST_F(AckTrackerTest, AckingTcp_Sack2) { make_pair(0, 10) )); EXPECT_EQ(3 + 10, tracker.acked_intervals().size()); + EXPECT_TRUE(tracker.is_segment_acked(maximum - 12, 2)); + EXPECT_TRUE(tracker.is_segment_acked(maximum - 2, 1)); + EXPECT_TRUE(tracker.is_segment_acked(2, 3)); + EXPECT_FALSE(tracker.is_segment_acked(maximum - 10, 10)); tracker.process_packet(make_tcp_ack(maximum - 2)); EXPECT_EQ(1 + 10, tracker.acked_intervals().size()); From 4123764a481151d9328d177bc687341a3573b3f1 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sun, 14 Feb 2016 08:56:25 -0800 Subject: [PATCH 17/19] Execute out of order callback even for seq < current_seq --- src/tcp_ip/flow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tcp_ip/flow.cpp b/src/tcp_ip/flow.cpp index 01d8f81..2650bbe 100644 --- a/src/tcp_ip/flow.cpp +++ b/src/tcp_ip/flow.cpp @@ -173,6 +173,9 @@ void Flow::process_packet(PDU& pdu) { } } } + else if (on_out_of_order_callback_) { + on_out_of_order_callback_(*this, tcp->seq(), raw->payload()); + } } void Flow::store_payload(uint32_t seq, payload_type payload) { From eb1c43d293d203218f4f85783b41a7b52642b1e0 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Sun, 14 Feb 2016 16:51:10 -0800 Subject: [PATCH 18/19] Throw proper exceptions --- include/tins/exceptions.h | 20 +++++++++ include/tins/tcp_ip/stream_follower.h | 64 +++++++++++++++++++-------- src/tcp_ip/stream.cpp | 12 ++--- src/tcp_ip/stream_follower.cpp | 46 +++++++++++++------ 4 files changed, 101 insertions(+), 41 deletions(-) diff --git a/include/tins/exceptions.h b/include/tins/exceptions.h index 75d449f..05e0d38 100644 --- a/include/tins/exceptions.h +++ b/include/tins/exceptions.h @@ -294,6 +294,26 @@ public: } }; +/** + * \brief Exception thrown when a required callback for an object is not set + */ +class callback_not_set : public exception_base { +public: + const char* what() const throw() { + return "Callback not set"; + } +}; + +/** + * \brief Exception thrown when an invalid packet is provided to some function + */ +class invalid_packet : public exception_base { +public: + const char* what() const throw() { + return "Invalid packet"; + } +}; + namespace Crypto { namespace WPA2 { /** diff --git a/include/tins/tcp_ip/stream_follower.h b/include/tins/tcp_ip/stream_follower.h index da6732f..a648664 100644 --- a/include/tins/tcp_ip/stream_follower.h +++ b/include/tins/tcp_ip/stream_follower.h @@ -84,7 +84,8 @@ public: */ enum TerminationReason { TIMEOUT, ///< The stream was terminated due to a timeout - BUFFERED_DATA ///< The stream was terminated because it had too much buffered data + BUFFERED_DATA, ///< The stream was terminated because it had too much buffered data + SACKED_SEGMENTS ///< The stream was terminated because it had too many SACKed segments }; /** @@ -94,6 +95,45 @@ public: */ typedef std::function stream_termination_callback_type; + /** + * \brief Unique identifies a stream. + * + * This struct is used to track TCP streams. It keeps track of minimum and maximum + * addresses/ports in a stream to match packets coming from any of the 2 endpoints + * into the same object. + */ + struct stream_id { + /** + * The type used to store each endpoint's address + */ + typedef std::array address_type; + + /** + * Default constructor + */ + stream_id(); + + /** + * Constructs a stream_id + * + * \param client_addr Client's address + * \param client_port Port's port + * \param server_addr Server's address + * \param server_port Server's port + */ + stream_id(const address_type& client_addr, uint16_t client_port, + const address_type& server_addr, uint16_t server_port); + + address_type min_address; + address_type max_address; + uint16_t min_address_port; + uint16_t max_address_port; + + bool operator<(const stream_id& rhs) const; + + static size_t hash(const stream_id& id); + }; + /** * Default constructor */ @@ -177,33 +217,19 @@ public: Stream& find_stream(const IPv6Address& client_addr, uint16_t client_port, const IPv6Address& server_addr, uint16_t server_port); private: - typedef std::array address_type; typedef Stream::timestamp_type timestamp_type; static const size_t DEFAULT_MAX_BUFFERED_CHUNKS; + static const size_t DEFAULT_MAX_SACKED_INTERVALS; static const uint32_t DEFAULT_MAX_BUFFERED_BYTES; static const timestamp_type DEFAULT_KEEP_ALIVE; - struct stream_id { - stream_id(const address_type& client_addr, uint16_t client_port, - const address_type& server_addr, uint16_t server_port); - - address_type min_address; - address_type max_address; - uint16_t min_address_port; - uint16_t max_address_port; - - bool operator<(const stream_id& rhs) const; - - static size_t hash(const stream_id& id); - }; - typedef std::map streams_type; - stream_id make_stream_id(const PDU& packet); + static stream_id make_stream_id(const PDU& packet); Stream& find_stream(const stream_id& id); - static address_type serialize(IPv4Address address); - static address_type serialize(const IPv6Address& address); + static stream_id::address_type serialize(IPv4Address address); + static stream_id::address_type serialize(const IPv6Address& address); void process_packet(PDU& packet, const timestamp_type& ts); void cleanup_streams(const timestamp_type& now); diff --git a/src/tcp_ip/stream.cpp b/src/tcp_ip/stream.cpp index a2f1785..0e5c618 100644 --- a/src/tcp_ip/stream.cpp +++ b/src/tcp_ip/stream.cpp @@ -208,8 +208,7 @@ const Stream::timestamp_type& Stream::last_seen() const { Flow Stream::extract_client_flow(const PDU& packet) { const TCP* tcp = packet.find_pdu(); if (!tcp) { - // TODO: define proper exception - throw runtime_error("No TCP"); + throw invalid_packet(); } if (const IP* ip = packet.find_pdu()) { return Flow(ip->dst_addr(), tcp->dport(), tcp->seq()); @@ -218,16 +217,14 @@ Flow Stream::extract_client_flow(const PDU& packet) { return Flow(ip->dst_addr(), tcp->dport(), tcp->seq()); } else { - // TODO: define proper exception - throw runtime_error("No valid layer 3"); + throw invalid_packet(); } } Flow Stream::extract_server_flow(const PDU& packet) { const TCP* tcp = packet.find_pdu(); if (!tcp) { - // TODO: define proper exception - throw runtime_error("No TCP"); + throw invalid_packet(); } if (const IP* ip = packet.find_pdu()) { return Flow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); @@ -236,8 +233,7 @@ Flow Stream::extract_server_flow(const PDU& packet) { return Flow(ip->src_addr(), tcp->sport(), tcp->ack_seq()); } else { - // TODO: define proper exception - throw runtime_error("No valid layer 3"); + throw invalid_packet(); } } diff --git a/src/tcp_ip/stream_follower.cpp b/src/tcp_ip/stream_follower.cpp index 54ea341..d0a2d38 100644 --- a/src/tcp_ip/stream_follower.cpp +++ b/src/tcp_ip/stream_follower.cpp @@ -62,6 +62,7 @@ namespace Tins { namespace TCPIP { const size_t StreamFollower::DEFAULT_MAX_BUFFERED_CHUNKS = 512; +const size_t StreamFollower::DEFAULT_MAX_SACKED_INTERVALS = 1024; const uint32_t StreamFollower::DEFAULT_MAX_BUFFERED_BYTES = 3 * 1024 * 1024; // 3MB const StreamFollower::timestamp_type StreamFollower::DEFAULT_KEEP_ALIVE = minutes(5); @@ -83,24 +84,26 @@ void StreamFollower::process_packet(Packet& packet) { } void StreamFollower::process_packet(PDU& packet, const timestamp_type& ts) { + const TCP* tcp = packet.find_pdu(); + if (!tcp) { + return; + } stream_id identifier = make_stream_id(packet); streams_type::iterator iter = streams_.find(identifier); bool process = true; if (iter == streams_.end()) { - const TCP& tcp = packet.rfind_pdu(); // Start tracking if they're either SYNs or they contain data (attach // to an already running flow). - if (tcp.flags() == TCP::SYN || (attach_to_flows_ && tcp.find_pdu() != 0)) { + if (tcp->flags() == TCP::SYN || (attach_to_flows_ && tcp->find_pdu() != 0)) { iter = streams_.insert(make_pair(identifier, Stream(packet, ts))).first; iter->second.setup_flows_callbacks(); if (on_new_connection_) { on_new_connection_(iter->second); } else { - // TODO: use proper exception - throw runtime_error("No new connection callback set"); + throw callback_not_set(); } - if (tcp.flags() == TCP::SYN) { + if (tcp->flags() == TCP::SYN) { process = false; } else { @@ -118,16 +121,27 @@ void StreamFollower::process_packet(PDU& packet, const timestamp_type& ts) { if (process) { Stream& stream = iter->second; stream.process_packet(packet, ts); + // Check for different potential termination size_t total_chunks = stream.client_flow().buffered_payload().size() + stream.server_flow().buffered_payload().size(); uint32_t total_buffered_bytes = stream.client_flow().total_buffered_bytes() + stream.server_flow().total_buffered_bytes(); bool terminate_stream = total_chunks > max_buffered_chunks_ || total_buffered_bytes > max_buffered_bytes_; + TerminationReason reason = BUFFERED_DATA; + #ifdef HAVE_ACK_TRACKER + if (!terminate_stream) { + uint32_t count = 0; + count += stream.client_flow().ack_tracker().acked_intervals().iterative_size(); + count += stream.server_flow().ack_tracker().acked_intervals().iterative_size(); + terminate_stream = count > DEFAULT_MAX_SACKED_INTERVALS; + reason = SACKED_SEGMENTS; + } + #endif // HAVE_ACK_TRACKER if (stream.is_finished() || terminate_stream) { // If we're terminating the stream, execute the termination callback if (terminate_stream && on_stream_termination_) { - on_stream_termination_(stream, BUFFERED_DATA); + on_stream_termination_(stream, reason); } streams_.erase(iter); } @@ -162,8 +176,7 @@ Stream& StreamFollower::find_stream(const IPv6Address& client_addr, uint16_t cli StreamFollower::stream_id StreamFollower::make_stream_id(const PDU& packet) { const TCP* tcp = packet.find_pdu(); if (!tcp) { - // TODO: define proper exception - throw runtime_error("No TCP"); + throw invalid_packet(); } if (const IP* ip = packet.find_pdu()) { return stream_id(serialize(ip->src_addr()), tcp->sport(), @@ -174,8 +187,7 @@ StreamFollower::stream_id StreamFollower::make_stream_id(const PDU& packet) { serialize(ip->dst_addr()), tcp->dport()); } else { - // TODO: define proper exception - throw runtime_error("No layer 3"); + throw invalid_packet(); } } @@ -189,16 +201,16 @@ Stream& StreamFollower::find_stream(const stream_id& id) { } } -StreamFollower::address_type StreamFollower::serialize(IPv4Address address) { - address_type addr; +StreamFollower::stream_id::address_type StreamFollower::serialize(IPv4Address address) { + stream_id::address_type addr; OutputMemoryStream output(addr.data(), addr.size()); addr.fill(0); output.write(address); return addr; } -StreamFollower::address_type StreamFollower::serialize(const IPv6Address& address) { - address_type addr; +StreamFollower::stream_id::address_type StreamFollower::serialize(const IPv6Address& address) { + stream_id::address_type addr; OutputMemoryStream output(addr.data(), addr.size()); addr.fill(0); output.write(address); @@ -224,6 +236,12 @@ void StreamFollower::cleanup_streams(const timestamp_type& now) { // stream_id +StreamFollower::stream_id::stream_id() +: min_address_port(0), max_address_port(0) { + min_address.fill(0); + max_address.fill(0); +} + StreamFollower::stream_id::stream_id(const address_type& client_addr, uint16_t client_port, const address_type& server_addr, From 91a724fe2d21a780b571e7b0ea40ac5364c3fdcc Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Mon, 15 Feb 2016 08:29:14 -0800 Subject: [PATCH 19/19] Add HTTP requests example --- examples/CMakeLists.txt | 10 +++ examples/http_requests.cpp | 145 +++++++++++++++++++++++++++++++++++ examples/stream_dump.cpp | 5 +- include/tins/tcp_ip/stream.h | 19 ++++- src/tcp_ip/stream.cpp | 17 +++- 5 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 examples/http_requests.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3e2a9bf..2f9014d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,6 @@ FIND_PACKAGE(libtins QUIET) FIND_PACKAGE(Threads QUIET) +FIND_PACKAGE(Boost COMPONENTS regex) IF(libtins_FOUND) SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/examples) @@ -22,6 +23,11 @@ IF(libtins_FOUND) traceroute wps_detect ) + IF(Boost_REGEX_FOUND) + SET(LIBTINS_CXX11_EXAMPLES ${LIBTINS_CXX11_EXAMPLES} http_requests) + ELSE() + MESSAGE(WARNING "Disabling HTTP requests example since boost.regex was not found") + ENDIF() ELSE(HAVE_CXX11) MESSAGE(WARNING "Disabling some examples since C++11 support is disabled.") ENDIF(HAVE_CXX11) @@ -46,6 +52,10 @@ IF(libtins_FOUND) ADD_EXECUTABLE(interfaces_info EXCLUDE_FROM_ALL interfaces_info.cpp) ADD_EXECUTABLE(tcp_connection_close EXCLUDE_FROM_ALL tcp_connection_close.cpp) ADD_EXECUTABLE(wps_detect EXCLUDE_FROM_ALL wps_detect.cpp) + IF (Boost_REGEX_FOUND) + ADD_EXECUTABLE(http_requests EXCLUDE_FROM_ALL http_requests.cpp) + TARGET_LINK_LIBRARIES(http_requests ${Boost_LIBRARIES}) + ENDIF() ENDIF(HAVE_CXX11) ADD_EXECUTABLE(beacon_display EXCLUDE_FROM_ALL beacon_display.cpp) diff --git a/examples/http_requests.cpp b/examples/http_requests.cpp new file mode 100644 index 0000000..047d315 --- /dev/null +++ b/examples/http_requests.cpp @@ -0,0 +1,145 @@ +/* + * 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 +#include +#include +#include +#include "tins/tcp_ip/stream_follower.h" +#include "tins/sniffer.h" + +using std::string; +using std::cout; +using std::cerr; +using std::endl; +using std::exception; + +using boost::regex; +using boost::match_results; + +using Tins::Packet; +using Tins::Sniffer; +using Tins::SnifferConfiguration; +using Tins::TCPIP::Stream; +using Tins::TCPIP::StreamFollower; + +// This example captures and follows TCP streams seen on port 80. It will +// wait until both the client and server send data and then apply a regex +// to both payloads, extrating some information and printing it. + +// Don't buffer more than 3kb of data in either request/response +const size_t MAX_PAYLOAD = 3 * 1024; +// The regex to be applied on the request. This will extract the HTTP +// method being used, the request's path and the Host header value. +regex request_regex("([\\w]+) ([^ ]+).+\r\nHost: ([\\d\\w\\.-]+)\r\n"); +// The regex to be applied on the response. This finds the response code. +regex response_regex("HTTP/[^ ]+ ([\\d]+)"); + +void on_server_data(Stream& stream) { + match_results client_match; + match_results server_match; + const Stream::payload_type& client_payload = stream.client_payload(); + const Stream::payload_type& server_payload = stream.server_payload(); + // Run the regexes on client/server payloads + bool valid = regex_search(server_payload.begin(), server_payload.end(), + server_match, response_regex) && + regex_search(client_payload.begin(), client_payload.end(), + client_match, request_regex); + // If we matched both the client and the server regexes + if (valid) { + // Extract all fields + string method = string(client_match[1].first, client_match[1].second); + string url = string(client_match[2].first, client_match[2].second); + string host = string(client_match[3].first, client_match[3].second); + string response_code = string(server_match[1].first, server_match[1].second); + // Now print them + cout << method << " http://" << host << url << " -> " << response_code << endl; + + // Once we've seen the first request on this stream, ignore it + stream.ignore_client_data(); + stream.ignore_server_data(); + } + + // Just in case the server returns invalid data, stop at 3kb + if (stream.server_payload().size() > MAX_PAYLOAD) { + stream.ignore_server_data(); + } +} + +void on_client_data(Stream& stream) { + // Don't hold more than 3kb of data from the client's flow + if (stream.client_payload().size() > MAX_PAYLOAD) { + stream.ignore_client_data(); + } +} + +void on_new_connection(Stream& stream) { + stream.client_data_callback(&on_client_data); + stream.server_data_callback(&on_server_data); + // Don't automatically cleanup the stream's data, as we'll manage + // the buffer ourselves and let it grow until we see a full request + // and response + stream.auto_cleanup_payloads(false); +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + cout << "Usage: " << argv[0] << " " << endl; + return 1; + } + + try { + // Construct the sniffer configuration object + SnifferConfiguration config; + // Only capture TCP traffic sent from/to port 80 + config.set_filter("tcp port 80"); + // Construct the sniffer we'll use + Sniffer sniffer(argv[1], config); + + cout << "Starting capture on interface " << argv[1] << endl; + + // Now construct the stream follower + StreamFollower follower; + // We just need to specify the callback to be executed when a new + // stream is captured. In this stream, you should define which callbacks + // will be executed whenever new data is sent on that stream + // (see on_new_connection) + follower.new_stream_callback(&on_new_connection); + // Now start capturing. Every time there's a new packet, call + // follower.process_packet + sniffer.sniff_loop([&](Packet& packet) { + follower.process_packet(packet); + return true; + }); + } + catch (exception& ex) { + cerr << "Error: " << ex.what() << endl; + return 1; + } +} diff --git a/examples/stream_dump.cpp b/examples/stream_dump.cpp index c1028f4..1ae5e6a 100644 --- a/examples/stream_dump.cpp +++ b/examples/stream_dump.cpp @@ -129,13 +129,12 @@ int main(int argc, char* argv[]) { cout << "Usage: " << argv[0] << " " << endl; return 1; } - using std::placeholders::_1; try { // Construct the sniffer configuration object SnifferConfiguration config; - // Only capture TCP traffic sent from/to port 80 - config.set_filter("tcp port " + string(argv[2])); + // Only capture TCP traffic sent from/to the given port + config.set_filter("tcp port " + to_string(stoi(string(argv[2])))); // Construct the sniffer we'll use Sniffer sniffer(argv[1], config); diff --git a/include/tins/tcp_ip/stream.h b/include/tins/tcp_ip/stream.h index 9f4eded..e55928c 100644 --- a/include/tins/tcp_ip/stream.h +++ b/include/tins/tcp_ip/stream.h @@ -335,6 +335,22 @@ public: */ void auto_cleanup_payloads(bool value); + /** + * \brief Indicates whether the client flow's payloads should be + * automatically erased. + * + * \sa auto_cleanup_payloads + */ + void auto_cleanup_client_data(bool value); + + /** + * \brief Indicates whether the server flow's payloads should be + * automatically erased. + * + * \sa auto_cleanup_payloads + */ + void auto_cleanup_server_data(bool value); + /** * Enables tracking of acknowledged segments * @@ -370,7 +386,8 @@ private: hwaddress_type server_hw_addr_; timestamp_type create_time_; timestamp_type last_seen_; - bool auto_cleanup_; + bool auto_cleanup_client_; + bool auto_cleanup_server_; }; } // TCPIP diff --git a/src/tcp_ip/stream.cpp b/src/tcp_ip/stream.cpp index 0e5c618..d0910e5 100644 --- a/src/tcp_ip/stream.cpp +++ b/src/tcp_ip/stream.cpp @@ -61,7 +61,7 @@ namespace TCPIP { Stream::Stream(PDU& packet, const timestamp_type& ts) : client_flow_(extract_client_flow(packet)), server_flow_(extract_server_flow(packet)), create_time_(ts), - last_seen_(ts), auto_cleanup_(true) { + last_seen_(ts), auto_cleanup_client_(true), auto_cleanup_server_(true) { // Update client flow state client_flow().process_packet(packet); const EthernetII* eth = packet.find_pdu(); @@ -249,7 +249,16 @@ void Stream::setup_flows_callbacks() { } void Stream::auto_cleanup_payloads(bool value) { - auto_cleanup_ = value; + auto_cleanup_client_data(value); + auto_cleanup_server_data(value); +} + +void Stream::auto_cleanup_client_data(bool value) { + auto_cleanup_client_ = value; +} + +void Stream::auto_cleanup_server_data(bool value) { + auto_cleanup_client_ = value; } void Stream::enable_ack_tracking() { @@ -265,7 +274,7 @@ void Stream::on_client_flow_data(const Flow& /*flow*/) { if (on_client_data_callback_) { on_client_data_callback_(*this); } - if (auto_cleanup_) { + if (auto_cleanup_client_) { client_payload().clear(); } } @@ -274,7 +283,7 @@ void Stream::on_server_flow_data(const Flow& /*flow*/) { if (on_server_data_callback_) { on_server_data_callback_(*this); } - if (auto_cleanup_) { + if (auto_cleanup_server_) { server_payload().clear(); } }