1
0
mirror of https://github.com/mfontanini/libtins synced 2026-01-23 02:35:57 +01:00

Document new TCP stream classes

This commit is contained in:
Matias Fontanini
2016-02-07 19:24:46 -08:00
parent 07b5d74179
commit 5b60b79fd8
6 changed files with 444 additions and 55 deletions

View File

@@ -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:

View File

@@ -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.

View File

@@ -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 {
/**

View File

@@ -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<uint8_t> payload_type;
/**
* The type used to store the buffered payload
*/
typedef std::map<uint32_t, payload_type> buffered_payload_type;
/**
* The type used to store the callbacks that this class triggers
*/
typedef std::function<void(Flow&)> event_callback;
Flow(const IPv4Address& 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);
Flow(const IPv6Address& dest_address, uint16_t dest_port,
/**
* 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<void(Stream&)> 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 <b>will not</b> 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);
/**
* 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<uint8_t, 16> address_type;
@@ -205,6 +567,7 @@ private:
typedef std::map<stream_id, Stream> 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);

View File

@@ -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);
}
@@ -283,13 +283,12 @@ void Flow::state(State new_state) {
Stream::Stream(const PDU& packet)
: client_flow_(extract_client_flow(packet)),
server_flow_(extract_server_flow(packet)) {
server_flow_(extract_server_flow(packet)), auto_cleanup_(true) {
const TCP& tcp = packet.rfind_pdu<TCP>();
// If it's a SYN, set the proper state
if (tcp.flags() == TCP::SYN) {
client_flow().state(Flow::SYN_SENT);
}
Stream::Stream(const Flow& client_flow, const Flow& server_flow)
: client_flow_(client_flow), server_flow_(server_flow) {
}
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());

View File

@@ -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<TCP>().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<TCP>().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) {