if ((!is_file_type_enabled()) and (!is_file_signature_enabled()))
{
update_file_size(data_size, position);
+ processing_complete = true;
if (PacketTracer::is_active())
PacketTracer::log("File: Type and Sig not enabled\n");
return false;
if(!file_flows)
return;
- if (continue_inspecting_file or position == SNORT_FILE_END)
+ if (continue_inspecting_file)
{
if (session_base_file_id)
{
Http2DummyPacket dummy_pkt;
dummy_pkt.flow = session_data->flow;
uint32_t unused = 0;
- session_data->get_current_stream(source_id)->get_hi_flow_data()->
- finish_h2_body(source_id);
+ Http2Stream* const stream = session_data->get_current_stream(source_id);
+ stream->get_hi_flow_data()->finish_h2_body(source_id);
+ stream->set_last_data_flush(source_id);
const snort::StreamSplitter::Status scan_result = session_data->hi_ss[source_id]->scan(
&dummy_pkt, nullptr, 0, unused, &http_flush_offset);
assert(scan_result == snort::StreamSplitter::FLUSH);
#include "http2_dummy_packet.h"
#include "http2_flow_data.h"
+#include "http2_module.h"
using namespace HttpCommon;
using namespace snort;
+using namespace Http2Enums;
Http2DataFrame::Http2DataFrame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data_,
- HttpCommon::SourceId source_id_) :
- Http2Frame(header_buffer, header_len, nullptr, 0, session_data_, source_id_)
+ HttpCommon::SourceId source_id_, Http2Stream* stream_) :
+ Http2Frame(header_buffer, header_len, nullptr, 0, session_data_, source_id_, stream_),
+ data_length(data_len)
{
if ((data_len != 0) || !session_data->flushing_data[source_id])
{
session_data->hi->clear(&dummy_pkt);
}
+void Http2DataFrame::update_stream_state()
+{
+ switch (stream->get_state(source_id))
+ {
+ case STATE_OPEN:
+ if (data_length > 0)
+ {
+ session_data->concurrent_files += 1;
+ stream->set_state(source_id, STATE_OPEN_DATA);
+ if (session_data->concurrent_files >
+ Http2Module::get_peg_counts(PEG_MAX_CONCURRENT_FILES))
+ {
+ Http2Module::increment_peg_counts(PEG_MAX_CONCURRENT_FILES);
+ }
+ }
+ if (stream->is_last_data_flush(source_id))
+ stream->set_state(source_id, STATE_CLOSED);
+ break;
+ case STATE_OPEN_DATA:
+ if (stream->is_last_data_flush(source_id))
+ {
+ assert(session_data->concurrent_files > 0);
+ session_data->concurrent_files -= 1;
+ stream->set_state(source_id, STATE_CLOSED);
+ }
+ break;
+ default:
+ // Stream state is idle or closed - this is caught in scan so should not get here
+ assert(false);
+ }
+}
#include "http2_frame.h"
class Http2Frame;
+class Http2Stream;
class Http2DataFrame : public Http2Frame
{
uint32_t get_xtradata_mask() override { return xtradata_mask; }
bool is_detection_required() const override { return detection_required; }
+ void update_stream_state() override;
friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
- const int32_t, Http2FlowData*, HttpCommon::SourceId);
+ const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
private:
- Http2DataFrame(const uint8_t* header_buffer, const int32_t header_len,const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
- HttpCommon::SourceId src_id);
+ Http2DataFrame(const uint8_t* header_buffer, const int32_t header_len,
+ const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
+ HttpCommon::SourceId src_id, Http2Stream* stream);
+ uint32_t data_length = 0;
uint32_t xtradata_mask = 0;
bool detection_required = false;
};
FT_PUSH_PROMISE=5, FT_PING=6, FT_GOAWAY=7, FT_WINDOW_UPDATE=8, FT_CONTINUATION=9, FT__ABORT=254,
FT__NONE=255 };
+enum StreamState { STATE_IDLE, STATE_OPEN, STATE_OPEN_DATA, STATE_CLOSED };
+
// Message buffers available to clients
// This enum must remain synchronized with Http2Api::classic_buffer_names[]
enum HTTP2_BUFFER { HTTP2_BUFFER_FRAME_HEADER = 1, HTTP2_BUFFER_FRAME_DATA,
// Peg counts
// This enum must remain synchronized with Http2Module::peg_names[] in http2_tables.cc
enum PEG_COUNT { PEG_FLOW = 0, PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS,
- PEG_MAX_ENTRIES, PEG_COUNT__MAX };
+ PEG_MAX_TABLE_ENTRIES, PEG_MAX_CONCURRENT_FILES, PEG_COUNT__MAX };
enum EventSid
{
INF_INVALID_STARTLINE = 25,
INF_INVALID_HEADER = 26,
INF_PADDING_LEN = 27,
+ INF_TRAILERS_AFTER_END_STREAM = 28,
INF__MAX_VALUE
};
Http2ConnectionSettings connection_settings[2];
Http2HpackDecoder hpack_decoder[2];
std::list<class StreamInfo> streams;
+ uint32_t concurrent_files = 0;
// Internal to scan()
bool preface[2] = { true, false };
#include "http2_flow_data.h"
#include "http2_headers_frame.h"
#include "http2_settings_frame.h"
+#include "http2_stream.h"
#include "service_inspectors/http_inspect/http_field.h"
using namespace HttpCommon;
Http2Frame::Http2Frame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data,
- SourceId source_id) : session_data(session_data), source_id(source_id)
+ SourceId source_id, Http2Stream* stream_) : session_data(session_data), source_id(source_id),
+ stream(stream_)
{
if (header_len > 0)
header.set(header_len, header_buffer, true);
if (data_len > 0)
- {
data.set(data_len, data_buffer, true);
- }
}
Http2Frame* Http2Frame::new_frame(const uint8_t* header, const int32_t header_len,
- const uint8_t* data, const int32_t data_len, Http2FlowData* session_data, SourceId source_id)
+ const uint8_t* data, const int32_t data_len, Http2FlowData* session_data, SourceId source_id,
+ Http2Stream* stream)
{
// FIXIT-E call the appropriate frame subclass constructor based on the type
switch(session_data->frame_type[source_id])
{
case FT_HEADERS:
return new Http2HeadersFrame(header, header_len, data, data_len, session_data,
- source_id);
+ source_id, stream);
case FT_SETTINGS:
return new Http2SettingsFrame(header, header_len, data, data_len, session_data,
- source_id);
+ source_id, stream);
case FT_DATA:
- return new Http2DataFrame(header, header_len, data, data_len, session_data, source_id);
+ return new Http2DataFrame(header, header_len, data, data_len, session_data, source_id,
+ stream);
default:
- return new Http2Frame(header, header_len, data, data_len, session_data, source_id);
+ return new Http2Frame(header, header_len, data, data_len, session_data, source_id, stream);
}
}
*/
class Http2FlowData;
+class Http2Stream;
class Http2Frame
{
virtual ~Http2Frame() { }
static Http2Frame* new_frame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data,
- HttpCommon::SourceId source_id);
+ HttpCommon::SourceId source_id, Http2Stream* stream);
virtual void clear() { }
virtual const Field& get_buf(unsigned id);
virtual uint32_t get_xtradata_mask() { return 0; }
virtual bool is_detection_required() const { return true; }
+ virtual void update_stream_state() { }
#ifdef REG_TEST
virtual void print_frame(FILE* output);
protected:
Http2Frame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data,
- HttpCommon::SourceId source_id);
+ HttpCommon::SourceId source_id, Http2Stream* stream);
uint8_t get_flags();
uint32_t get_stream_id();
Field data;
Http2FlowData* session_data;
HttpCommon::SourceId source_id;
+ Http2Stream* stream;
const static uint8_t flags_index = 4;
const static uint8_t stream_id_index = 5;
#include "http2_flow_data.h"
#include "http2_hpack.h"
#include "http2_start_line.h"
+#include "http2_stream.h"
using namespace snort;
using namespace HttpCommon;
Http2HeadersFrame::Http2HeadersFrame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data_,
- HttpCommon::SourceId source_id_) :
- Http2Frame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_)
+ HttpCommon::SourceId source_id_, Http2Stream* stream_) :
+ Http2Frame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_, stream_)
{
- Http2Stream* const stream = session_data->find_stream(get_stream_id());
- if (get_flags() & END_STREAM)
- stream->set_end_stream(source_id);
+ // FIXIT-E If the stream state is not IDLE, we've already received the headers. Trailers are
+ // not yet being processed
+ if (stream->get_state(source_id) >= STATE_OPEN)
+ {
+ trailer = true;
+ return;
+ }
// No need to process an empty headers frame
if (data.length() <= 0)
}
}
+void Http2HeadersFrame::update_stream_state()
+{
+ switch (stream->get_state(source_id))
+ {
+ case STATE_IDLE:
+ if (get_flags() & END_STREAM)
+ stream->set_state(source_id, STATE_CLOSED);
+ else
+ stream->set_state(source_id, STATE_OPEN);
+ break;
+ case STATE_OPEN:
+ // fallthrough
+ case STATE_OPEN_DATA:
+ if (get_flags() & END_STREAM)
+ {
+ if (stream->get_state(source_id) == STATE_OPEN_DATA)
+ session_data->concurrent_files -= 1;
+ stream->set_state(source_id, STATE_CLOSED);
+ }
+ else
+ {
+ // Headers frame without end_stream flag set after initial Headers frame
+ *session_data->infractions[source_id] += INF_FRAME_SEQUENCE;
+ session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
+ }
+ break;
+ case STATE_CLOSED:
+ // Trailers in closed state
+ *session_data->infractions[source_id] += INF_TRAILERS_AFTER_END_STREAM;
+ session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
+ break;
+ }
+}
+
+
#ifdef REG_TEST
void Http2HeadersFrame::print_frame(FILE* output)
{
- fprintf(output, "\nHeaders frame\n");
+ if (!trailer)
+ fprintf(output, "\nHeaders frame\n");
+ else
+ fprintf(output, "Trailing Headers frame\n");
if (error_during_decode)
fprintf(output, "Error decoding headers.\n");
if (start_line)
class Http2HpackDecoder;
class Http2StartLine;
class Http2Frame;
+class Http2Stream;
class Http2HeadersFrame : public Http2Frame
{
const Field& get_buf(unsigned id) override;
uint32_t get_xtradata_mask() override { return xtradata_mask; }
bool is_detection_required() const override { return detection_required; }
+ void update_stream_state() override;
friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
- const int32_t, Http2FlowData*, HttpCommon::SourceId);
+ const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
#ifdef REG_TEST
void print_frame(FILE* output) override;
private:
Http2HeadersFrame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
- HttpCommon::SourceId src_id);
+ HttpCommon::SourceId src_id, Http2Stream* stream);
Http2StartLine* start_line_generator = nullptr;
uint8_t* decoded_headers = nullptr; // working buffer to store decoded headers
bool hi_abort = false;
uint32_t xtradata_mask = 0;
bool detection_required = false;
+
+ // FIXIT-E Process trailers
+ bool trailer = false;
};
#endif
circular_array[start] = new_entry;
num_entries++;
- if (num_entries > Http2Module::get_peg_counts(PEG_MAX_ENTRIES))
- Http2Module::increment_peg_counts(PEG_MAX_ENTRIES);
+ if (num_entries > Http2Module::get_peg_counts(PEG_MAX_TABLE_ENTRIES))
+ Http2Module::increment_peg_counts(PEG_MAX_TABLE_ENTRIES);
rfc_table_size += new_entry_size;
return true;
Http2SettingsFrame::Http2SettingsFrame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
- HttpCommon::SourceId src_id) : Http2Frame(header_buffer, header_len, data_buffer, data_len,
- ssn_data, src_id)
+ HttpCommon::SourceId src_id, Http2Stream* stream_) : Http2Frame(header_buffer, header_len, data_buffer, data_len,
+ ssn_data, src_id, stream_)
{
if (!sanity_check())
{
{
public:
friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
- const int32_t, Http2FlowData*, HttpCommon::SourceId);
+ const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
bool is_detection_required() const override { return false; }
#ifdef REG_TEST
private:
Http2SettingsFrame(const uint8_t* header_buffer, const int32_t header_len,
const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
- HttpCommon::SourceId src_id);
+ HttpCommon::SourceId src_id, Http2Stream* stream);
void parse_settings_frame();
bool sanity_check();
#include "config.h"
#endif
+#include "http2_enum.h"
#include "http2_stream.h"
#include "service_inspectors/http_inspect/http_flow_data.h"
#include "http2_data_cutter.h"
using namespace HttpCommon;
+using namespace Http2Enums;
Http2Stream::Http2Stream(uint32_t stream_id_, Http2FlowData* session_data_) :
stream_id(stream_id_),
{
delete current_frame;
current_frame = Http2Frame::new_frame(header_buffer, header_len, data_buffer,
- data_len, session_data, source_id);
+ data_len, session_data, source_id, this);
+ current_frame->update_stream_state();
}
void Http2Stream::clear_frame()
data_cutter[source_id] = new Http2DataCutter(session_data, source_id);
return data_cutter[source_id];
}
+
+bool Http2Stream::is_open(HttpCommon::SourceId source_id)
+{
+ return (state[source_id] == STATE_OPEN) || (state[source_id] == STATE_OPEN_DATA);
+}
#include "service_inspectors/http_inspect/http_common.h"
#include "service_inspectors/http_inspect/http_field.h"
+#include "http2_enum.h"
#include "http2_frame.h"
class Http2DataCutter;
void set_data_cutter(Http2DataCutter* cutter, HttpCommon::SourceId source_id)
{ data_cutter[source_id] = cutter; }
- void set_end_stream(HttpCommon::SourceId source_id) { end_stream_set[source_id] = true; }
- bool end_stream_is_set(HttpCommon::SourceId source_id) { return end_stream_set[source_id]; }
-
void set_partial_buf_pending(HttpCommon::SourceId source_id)
{ partial_buf_pending[source_id] = true; }
void reset_partial_buf_pending(HttpCommon::SourceId source_id)
bool is_partial_buf_pending(HttpCommon::SourceId source_id)
{ return partial_buf_pending[source_id]; }
+ void set_state(HttpCommon::SourceId source_id, Http2Enums::StreamState new_state)
+ { state[source_id] = new_state; }
+ Http2Enums::StreamState get_state(HttpCommon::SourceId source_id) { return state[source_id]; }
+ bool is_open(HttpCommon::SourceId source_id);
+ void set_last_data_flush(HttpCommon::SourceId source_id) { last_data_flush[source_id] = true; }
+ bool is_last_data_flush(HttpCommon::SourceId source_id) { return last_data_flush[source_id]; }
+
#ifdef REG_TEST
void print_frame(FILE* output);
#endif
HttpFlowData* hi_flow_data = nullptr;
HttpMsgSection* hi_msg_section = nullptr;
Http2DataCutter* data_cutter[2] = { nullptr, nullptr};
- bool end_stream_set[2] = { false, false };
bool partial_buf_pending[2] = { false, false }; // used to indicate a partial buffer
- // is pending from a previous partial flush
+ bool last_data_flush[2] = { false, false };
+ Http2Enums::StreamState state[2] = { Http2Enums::STATE_IDLE, Http2Enums::STATE_IDLE };
};
#endif
{
Http2Stream* const stream = session_data->find_stream(session_data->current_stream[source_id]);
- if (!stream || stream->end_stream_is_set(source_id))
+ if (!stream || !stream->is_open(source_id))
{
*session_data->infractions[source_id] += INF_FRAME_SEQUENCE;
session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
+ // FIXIT-E We should not be aborting here
return StreamSplitter::ABORT;
}
*session_data->infractions[source_id] += INF_FRAME_SEQUENCE;
session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
+ // FIXIT-E We should not be aborting here
return StreamSplitter::ABORT;
}
session_data->scan_remaining_frame_octets[source_id] = frame_length;
session_data->total_bytes_in_split[source_id] += FRAME_HEADER_LENGTH +
frame_length;
-
- // If the stream object exists and the end_stream flag is set, save that state in the
- // stream object. If this is the first headers frame in the current stream, the stream
- // object has not been created yet. The end_stream flag will be handled in the headers
- // frame processing
- Http2Stream* const stream = session_data->find_stream(
- session_data->current_stream[source_id]);
- if (stream and frame_flags & END_STREAM)
- stream->set_end_stream(source_id);
}
// If we don't have the full frame, keep scanning
}
// Flush pending data
-void Http2StreamSplitter::partial_flush_data(Http2FlowData* session_data, HttpCommon::SourceId source_id,
- uint32_t* flush_offset, uint32_t data_offset, uint32_t old_stream)
+void Http2StreamSplitter::partial_flush_data(Http2FlowData* session_data,
+ HttpCommon::SourceId source_id, uint32_t* flush_offset, uint32_t data_offset, uint32_t
+ old_stream)
{
session_data->current_stream[source_id] = session_data->stream_in_hi = old_stream;
session_data->frame_type[source_id] = FT_DATA;
((old_stream != session_data->current_stream[source_id] && type == FT_DATA)
|| type != FT_DATA))
{
- partial_flush_data(session_data, source_id, flush_offset, data_offset, old_stream);
+ partial_flush_data(session_data, source_id, flush_offset, data_offset,
+ old_stream);
return StreamSplitter::FLUSH;
}
const PegInfo Http2Module::peg_names[PEG_COUNT__MAX+1] =
{
- { CountType::SUM, "flows", "HTTP connections inspected" },
+ { CountType::SUM, "flows", "HTTP/2 connections inspected" },
{ CountType::NOW, "concurrent_sessions", "total concurrent HTTP/2 sessions" },
{ CountType::MAX, "max_concurrent_sessions", "maximum concurrent HTTP/2 sessions" },
{ CountType::MAX, "max_table_entries", "maximum entries in an HTTP/2 dynamic table" },
+ { CountType::MAX, "max_concurrent_files", "maximum concurrent file transfers per HTTP/2 "
+ "connection" },
{ CountType::END, nullptr, nullptr }
};
{
Profile profile(HttpModule::get_profile_stats());
- assert(source_id == SRC_SERVER);
-
HttpFlowData* session_data = HttpInspect::http_get_flow_data(flow);
assert(session_data != nullptr);
+
+ assert(session_data->for_http2 || source_id == SRC_SERVER);
+
assert((session_data->type_expected[source_id] == SEC_BODY_CL) ||
(session_data->type_expected[source_id] == SEC_BODY_OLD) ||
(session_data->type_expected[source_id] == SEC_BODY_CHUNK) ||