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(); } }