forked from envoyproxy/envoy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
listener filter: new listener filter for inspecting http protocol (en…
…voyproxy#7559) Description: new listener filter for inspecting http protocol. - Http1x: check request line - Http2: check connection preface Pros: Performance; Cons: False positive possibility Risk Level: low Testing: unit test, manual test Docs Changes: Added Release Notes: Added #Issue: envoyproxy#7527 Signed-off-by: Yan Xue <[email protected]>
- Loading branch information
Showing
14 changed files
with
739 additions
and
6 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
docs/root/configuration/listener_filters/http_inspector.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
.. _config_listener_filters_http_inspector: | ||
|
||
HTTP Inspector | ||
============== | ||
|
||
HTTP Inspector listener filter allows detecting whether the application protocol appears to be HTTP, | ||
and if it is HTTP, it detects the HTTP protocol (HTTP/1.x or HTTP/2) further. This can be used to select a | ||
:ref:`FilterChain <envoy_api_msg_listener.FilterChain>` via the :ref:`application_protocols <envoy_api_field_listener.FilterChainMatch.application_protocols>` | ||
of a :ref:`FilterChainMatch <envoy_api_msg_listener.FilterChainMatch>`. | ||
|
||
* :ref:`v2 API reference <envoy_api_field_listener.ListenerFilter.name>` | ||
* This filter should be configured with the name *envoy.listener.http_inspector*. | ||
|
||
Example | ||
------- | ||
|
||
A sample filter configuration could be: | ||
|
||
.. code-block:: yaml | ||
listener_filters: | ||
- name: "envoy.listener.http_inspector" | ||
typed_config: {} | ||
Statistics | ||
---------- | ||
|
||
This filter has a statistics tree rooted at *http_inspector* with the following statistics: | ||
|
||
.. csv-table:: | ||
:header: Name, Type, Description | ||
:widths: 1, 1, 2 | ||
|
||
read_error, Counter, Total read errors | ||
http10_found, Counter, Total number of times HTTP/1.0 was found | ||
http11_found, Counter, Total number of times HTTP/1.1 was found | ||
http2_found, Counter, Total number of times HTTP/2 was found | ||
http_not_found, Counter, Total number of times HTTP protocol was not found |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
licenses(["notice"]) # Apache 2 | ||
|
||
# HTTP inspector filter for sniffing HTTP protocol and setting HTTP version to a FilterChain. | ||
|
||
load( | ||
"//bazel:envoy_build_system.bzl", | ||
"envoy_cc_library", | ||
"envoy_package", | ||
) | ||
|
||
envoy_package() | ||
|
||
envoy_cc_library( | ||
name = "http_inspector_lib", | ||
srcs = ["http_inspector.cc"], | ||
hdrs = ["http_inspector.h"], | ||
deps = [ | ||
"//include/envoy/event:dispatcher_interface", | ||
"//include/envoy/event:timer_interface", | ||
"//include/envoy/network:filter_interface", | ||
"//include/envoy/network:listen_socket_interface", | ||
"//source/common/api:os_sys_calls_lib", | ||
"//source/common/common:minimal_logger_lib", | ||
"//source/common/http:headers_lib", | ||
"//source/extensions/transport_sockets:well_known_names", | ||
], | ||
) | ||
|
||
envoy_cc_library( | ||
name = "config", | ||
srcs = ["config.cc"], | ||
deps = [ | ||
":http_inspector_lib", | ||
"//include/envoy/registry", | ||
"//include/envoy/server:filter_config_interface", | ||
"//source/extensions/filters/listener:well_known_names", | ||
], | ||
) |
43 changes: 43 additions & 0 deletions
43
source/extensions/filters/listener/http_inspector/config.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#include "envoy/registry/registry.h" | ||
#include "envoy/server/filter_config.h" | ||
|
||
#include "extensions/filters/listener/http_inspector/http_inspector.h" | ||
#include "extensions/filters/listener/well_known_names.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace ListenerFilters { | ||
namespace HttpInspector { | ||
|
||
/** | ||
* Config registration for the Http inspector filter. @see NamedNetworkFilterConfigFactory. | ||
*/ | ||
class HttpInspectorConfigFactory : public Server::Configuration::NamedListenerFilterConfigFactory { | ||
public: | ||
// NamedListenerFilterConfigFactory | ||
Network::ListenerFilterFactoryCb | ||
createFilterFactoryFromProto(const Protobuf::Message&, | ||
Server::Configuration::ListenerFactoryContext& context) override { | ||
ConfigSharedPtr config(std::make_shared<Config>(context.scope())); | ||
return [config](Network::ListenerFilterManager& filter_manager) -> void { | ||
filter_manager.addAcceptFilter(std::make_unique<Filter>(config)); | ||
}; | ||
} | ||
|
||
ProtobufTypes::MessagePtr createEmptyConfigProto() override { | ||
return std::make_unique<Envoy::ProtobufWkt::Empty>(); | ||
} | ||
|
||
std::string name() override { return ListenerFilterNames::get().HttpInspector; } | ||
}; | ||
|
||
/** | ||
* Static registration for the http inspector filter. @see RegisterFactory. | ||
*/ | ||
REGISTER_FACTORY(HttpInspectorConfigFactory, | ||
Server::Configuration::NamedListenerFilterConfigFactory); | ||
|
||
} // namespace HttpInspector | ||
} // namespace ListenerFilters | ||
} // namespace Extensions | ||
} // namespace Envoy |
145 changes: 145 additions & 0 deletions
145
source/extensions/filters/listener/http_inspector/http_inspector.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
#include "extensions/filters/listener/http_inspector/http_inspector.h" | ||
|
||
#include "envoy/event/dispatcher.h" | ||
#include "envoy/network/listen_socket.h" | ||
#include "envoy/stats/scope.h" | ||
|
||
#include "common/api/os_sys_calls_impl.h" | ||
#include "common/common/assert.h" | ||
#include "common/common/macros.h" | ||
#include "common/http/headers.h" | ||
|
||
#include "extensions/transport_sockets/well_known_names.h" | ||
|
||
#include "absl/strings/match.h" | ||
#include "absl/strings/str_split.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace ListenerFilters { | ||
namespace HttpInspector { | ||
|
||
Config::Config(Stats::Scope& scope) | ||
: stats_{ALL_HTTP_INSPECTOR_STATS(POOL_COUNTER_PREFIX(scope, "http_inspector."))} {} | ||
|
||
const absl::string_view Filter::HTTP2_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; | ||
thread_local uint8_t Filter::buf_[Config::MAX_INSPECT_SIZE]; | ||
|
||
Filter::Filter(const ConfigSharedPtr config) : config_(config) {} | ||
|
||
Network::FilterStatus Filter::onAccept(Network::ListenerFilterCallbacks& cb) { | ||
ENVOY_LOG(debug, "http inspector: new connection accepted"); | ||
|
||
Network::ConnectionSocket& socket = cb.socket(); | ||
|
||
absl::string_view transport_protocol = socket.detectedTransportProtocol(); | ||
if (!transport_protocol.empty() && | ||
transport_protocol != TransportSockets::TransportSocketNames::get().RawBuffer) { | ||
ENVOY_LOG(trace, "http inspector: cannot inspect http protocol with transport socket {}", | ||
transport_protocol); | ||
return Network::FilterStatus::Continue; | ||
} | ||
|
||
ASSERT(file_event_ == nullptr); | ||
|
||
file_event_ = cb.dispatcher().createFileEvent( | ||
socket.ioHandle().fd(), | ||
[this](uint32_t events) { | ||
ASSERT(events == Event::FileReadyType::Read); | ||
onRead(); | ||
}, | ||
Event::FileTriggerType::Edge, Event::FileReadyType::Read); | ||
|
||
cb_ = &cb; | ||
return Network::FilterStatus::StopIteration; | ||
} | ||
|
||
void Filter::onRead() { | ||
auto& os_syscalls = Api::OsSysCallsSingleton::get(); | ||
const Api::SysCallSizeResult result = | ||
os_syscalls.recv(cb_->socket().ioHandle().fd(), buf_, Config::MAX_INSPECT_SIZE, MSG_PEEK); | ||
ENVOY_LOG(trace, "http inspector: recv: {}", result.rc_); | ||
if (result.rc_ == -1 && result.errno_ == EAGAIN) { | ||
return; | ||
} else if (result.rc_ < 0) { | ||
config_->stats().read_error_.inc(); | ||
done(false); | ||
return; | ||
} | ||
|
||
parseHttpHeader(absl::string_view(reinterpret_cast<const char*>(buf_), result.rc_)); | ||
} | ||
|
||
void Filter::parseHttpHeader(absl::string_view data) { | ||
if (absl::StartsWith(data, Filter::HTTP2_CONNECTION_PREFACE)) { | ||
ENVOY_LOG(trace, "http inspector: http2 connection preface found"); | ||
protocol_ = "HTTP/2"; | ||
done(true); | ||
} else { | ||
size_t pos = data.find_first_of("\r\n"); | ||
|
||
// Cannot find \r or \n | ||
if (pos == absl::string_view::npos) { | ||
ENVOY_LOG(trace, "http inspector: no request line detected"); | ||
return done(false); | ||
} | ||
|
||
absl::string_view request_line = data.substr(0, pos); | ||
std::vector<absl::string_view> fields = absl::StrSplit(request_line, absl::MaxSplits(' ', 4)); | ||
|
||
// Method SP Request-URI SP HTTP-Version | ||
if (fields.size() != 3) { | ||
ENVOY_LOG(trace, "http inspector: invalid http1x request line"); | ||
return done(false); | ||
} | ||
|
||
if (httpProtocols().count(fields[2]) == 0) { | ||
ENVOY_LOG(trace, "http inspector: protocol: {} not valid", fields[2]); | ||
return done(false); | ||
} | ||
|
||
ENVOY_LOG(trace, "http inspector: method: {}, request uri: {}, protocol: {}", fields[0], | ||
fields[1], fields[2]); | ||
|
||
protocol_ = fields[2]; | ||
done(true); | ||
} | ||
} | ||
|
||
void Filter::done(bool success) { | ||
ENVOY_LOG(trace, "http inspector: done: {}", success); | ||
|
||
if (success) { | ||
absl::string_view protocol; | ||
if (protocol_ == Http::Headers::get().ProtocolStrings.Http10String) { | ||
config_->stats().http10_found_.inc(); | ||
protocol = "http/1.0"; | ||
} else if (protocol_ == Http::Headers::get().ProtocolStrings.Http11String) { | ||
config_->stats().http11_found_.inc(); | ||
protocol = "http/1.1"; | ||
} else { | ||
ASSERT(protocol_ == "HTTP/2"); | ||
config_->stats().http2_found_.inc(); | ||
protocol = "h2"; | ||
} | ||
|
||
cb_->socket().setRequestedApplicationProtocols({protocol}); | ||
} else { | ||
config_->stats().http_not_found_.inc(); | ||
} | ||
|
||
file_event_.reset(); | ||
// Do not skip following listener filters. | ||
cb_->continueFilterChain(true); | ||
} | ||
|
||
const absl::flat_hash_set<std::string>& Filter::httpProtocols() const { | ||
CONSTRUCT_ON_FIRST_USE(absl::flat_hash_set<std::string>, | ||
Http::Headers::get().ProtocolStrings.Http10String, | ||
Http::Headers::get().ProtocolStrings.Http11String); | ||
} | ||
|
||
} // namespace HttpInspector | ||
} // namespace ListenerFilters | ||
} // namespace Extensions | ||
} // namespace Envoy |
83 changes: 83 additions & 0 deletions
83
source/extensions/filters/listener/http_inspector/http_inspector.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
#pragma once | ||
|
||
#include "envoy/event/file_event.h" | ||
#include "envoy/event/timer.h" | ||
#include "envoy/network/filter.h" | ||
#include "envoy/stats/scope.h" | ||
#include "envoy/stats/stats_macros.h" | ||
|
||
#include "common/common/logger.h" | ||
|
||
#include "absl/container/flat_hash_set.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace ListenerFilters { | ||
namespace HttpInspector { | ||
|
||
/** | ||
* All stats for the http inspector. @see stats_macros.h | ||
*/ | ||
#define ALL_HTTP_INSPECTOR_STATS(COUNTER) \ | ||
COUNTER(read_error) \ | ||
COUNTER(http10_found) \ | ||
COUNTER(http11_found) \ | ||
COUNTER(http2_found) \ | ||
COUNTER(http_not_found) | ||
|
||
/** | ||
* Definition of all stats for the Http inspector. @see stats_macros.h | ||
*/ | ||
struct HttpInspectorStats { | ||
ALL_HTTP_INSPECTOR_STATS(GENERATE_COUNTER_STRUCT) | ||
}; | ||
|
||
/** | ||
* Global configuration for http inspector. | ||
*/ | ||
class Config { | ||
public: | ||
Config(Stats::Scope& scope); | ||
|
||
const HttpInspectorStats& stats() const { return stats_; } | ||
|
||
static constexpr uint32_t MAX_INSPECT_SIZE = 8192; | ||
|
||
private: | ||
HttpInspectorStats stats_; | ||
}; | ||
|
||
using ConfigSharedPtr = std::shared_ptr<Config>; | ||
|
||
/** | ||
* Http inspector listener filter. | ||
*/ | ||
class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filter> { | ||
public: | ||
Filter(const ConfigSharedPtr config); | ||
|
||
// Network::ListenerFilter | ||
Network::FilterStatus onAccept(Network::ListenerFilterCallbacks& cb) override; | ||
|
||
private: | ||
static const absl::string_view HTTP2_CONNECTION_PREFACE; | ||
|
||
void onRead(); | ||
void done(bool success); | ||
void parseHttpHeader(absl::string_view data); | ||
|
||
const absl::flat_hash_set<std::string>& httpProtocols() const; | ||
|
||
ConfigSharedPtr config_; | ||
Network::ListenerFilterCallbacks* cb_{nullptr}; | ||
Event::FileEventPtr file_event_; | ||
absl::string_view protocol_; | ||
|
||
// Use static thread_local to avoid allocating buffer over and over again. | ||
static thread_local uint8_t buf_[Config::MAX_INSPECT_SIZE]; | ||
}; | ||
|
||
} // namespace HttpInspector | ||
} // namespace ListenerFilters | ||
} // namespace Extensions | ||
} // namespace Envoy |
Oops, something went wrong.