From af7181b925398d1153138b3d8f8854bd3a53f535 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Fri, 15 Jul 2022 18:32:36 -0700 Subject: [PATCH] lua: add new method to access network connection streamInfo() & dynamicMetadata() Signed-off-by: Rohit Agrawal --- .../extensions/filters/http/lua/lua_filter.cc | 14 +++ .../extensions/filters/http/lua/lua_filter.h | 10 +- .../extensions/filters/http/lua/wrappers.cc | 56 ++++++++++++ source/extensions/filters/http/lua/wrappers.h | 91 +++++++++++++++++++ .../filters/http/lua/lua_filter_test.cc | 49 ++++++++++ 5 files changed, 219 insertions(+), 1 deletion(-) diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index c5791a6a22aa..04ce9d600573 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -134,6 +134,9 @@ PerLuaCodeSetup::PerLuaCodeSetup(const std::string& lua_code, ThreadLocal::SlotA lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); + lua_state_.registerType(); + lua_state_.registerType(); + lua_state_.registerType(); const Filters::Common::Lua::InitializerList initializers( // EnvoyTimestampResolution "enum". @@ -511,6 +514,17 @@ int StreamHandleWrapper::luaStreamInfo(lua_State* state) { return 1; } +int StreamHandleWrapper::luaConnectionStreamInfo(lua_State* state) { + ASSERT(state_ == State::Running); + if (connection_stream_info_wrapper_.get() != nullptr) { + connection_stream_info_wrapper_.pushStack(); + } else { + connection_stream_info_wrapper_.reset( + ConnectionStreamInfoWrapper::create(state, callbacks_.connection()->streamInfo()), true); + } + return 1; +} + int StreamHandleWrapper::luaConnection(lua_State* state) { ASSERT(state_ == State::Running); if (connection_wrapper_.get() != nullptr) { diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index b389b6087c71..89e68a346c56 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -165,7 +165,8 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject trailers_wrapper_; Filters::Common::Lua::LuaDeathRef metadata_wrapper_; Filters::Common::Lua::LuaDeathRef stream_info_wrapper_; + Filters::Common::Lua::LuaDeathRef connection_stream_info_wrapper_; Filters::Common::Lua::LuaDeathRef connection_wrapper_; Filters::Common::Lua::LuaDeathRef public_key_wrapper_; State state_{State::Running}; diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index cae488bd67e0..87a22c4b8ccc 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -133,6 +133,16 @@ int StreamInfoWrapper::luaDynamicMetadata(lua_State* state) { return 1; } +int ConnectionStreamInfoWrapper::luaConnectionDynamicMetadata(lua_State* state) { + if (connection_dynamic_metadata_wrapper_.get() != nullptr) { + connection_dynamic_metadata_wrapper_.pushStack(); + } else { + connection_dynamic_metadata_wrapper_.reset( + ConnectionDynamicMetadataMapWrapper::create(state, *this), true); + } + return 1; +} + int StreamInfoWrapper::luaDownstreamSslConnection(lua_State* state) { const auto& ssl = stream_info_.downstreamAddressProvider().sslConnection(); if (ssl != nullptr) { @@ -174,6 +184,14 @@ DynamicMetadataMapIterator::DynamicMetadataMapIterator(DynamicMetadataMapWrapper StreamInfo::StreamInfo& DynamicMetadataMapWrapper::streamInfo() { return parent_.stream_info_; } +ConnectionDynamicMetadataMapIterator::ConnectionDynamicMetadataMapIterator( + ConnectionDynamicMetadataMapWrapper& parent) + : parent_{parent}, current_{parent_.streamInfo().dynamicMetadata().filter_metadata().begin()} {} + +const StreamInfo::StreamInfo& ConnectionDynamicMetadataMapWrapper::streamInfo() { + return parent_.connection_stream_info_; +} + int DynamicMetadataMapIterator::luaPairsIterator(lua_State* state) { if (current_ == parent_.streamInfo().dynamicMetadata().filter_metadata().end()) { parent_.iterator_.reset(); @@ -187,6 +205,20 @@ int DynamicMetadataMapIterator::luaPairsIterator(lua_State* state) { return 2; } +int ConnectionDynamicMetadataMapIterator::luaConnectionDynamicMetadataPairsIterator( + lua_State* state) { + if (current_ == parent_.streamInfo().dynamicMetadata().filter_metadata().end()) { + parent_.iterator_.reset(); + return 0; + } + + lua_pushlstring(state, current_->first.data(), current_->first.size()); + Filters::Common::Lua::MetadataMapHelper::createTable(state, current_->second.fields()); + + current_++; + return 2; +} + int DynamicMetadataMapWrapper::luaGet(lua_State* state) { const char* filter_name = luaL_checkstring(state, 2); const auto& metadata = streamInfo().dynamicMetadata().filter_metadata(); @@ -230,6 +262,30 @@ int DynamicMetadataMapWrapper::luaPairs(lua_State* state) { return 1; } +int ConnectionDynamicMetadataMapWrapper::luaConnectionDynamicMetadataGet(lua_State* state) { + const char* filter_name = luaL_checkstring(state, 2); + const auto& metadata = streamInfo().dynamicMetadata().filter_metadata(); + const auto filter_it = metadata.find(filter_name); + if (filter_it == metadata.end()) { + return 0; + } + + Filters::Common::Lua::MetadataMapHelper::createTable(state, filter_it->second.fields()); + return 1; +} + +int ConnectionDynamicMetadataMapWrapper::luaConnectionDynamicMetadataPairs(lua_State* state) { + if (iterator_.get() != nullptr) { + luaL_error(state, "cannot create a second iterator before completing the first"); + } + + iterator_.reset(ConnectionDynamicMetadataMapIterator::create(state, *this), true); + lua_pushcclosure( + state, ConnectionDynamicMetadataMapIterator::static_luaConnectionDynamicMetadataPairsIterator, + 1); + return 1; +} + int PublicKeyWrapper::luaGet(lua_State* state) { if (public_key_.empty()) { lua_pushnil(state); diff --git a/source/extensions/filters/http/lua/wrappers.h b/source/extensions/filters/http/lua/wrappers.h index 589565b720af..880dab3b1906 100644 --- a/source/extensions/filters/http/lua/wrappers.h +++ b/source/extensions/filters/http/lua/wrappers.h @@ -122,6 +122,8 @@ class HeaderMapWrapper : public Filters::Common::Lua::BaseLuaObject::const_iterator current_; }; +/** + * Iterator over a network filter dynamic metadata map. + */ +class ConnectionDynamicMetadataMapIterator + : public Filters::Common::Lua::BaseLuaObject { +public: + ConnectionDynamicMetadataMapIterator(ConnectionDynamicMetadataMapWrapper& parent); + + static ExportedFunctions exportedFunctions() { return {}; } + + DECLARE_LUA_CLOSURE(ConnectionDynamicMetadataMapIterator, + luaConnectionDynamicMetadataPairsIterator); + +private: + ConnectionDynamicMetadataMapWrapper& parent_; + Protobuf::Map::const_iterator current_; +}; + /** * Lua wrapper for a dynamic metadata. */ @@ -190,6 +210,48 @@ class DynamicMetadataMapWrapper friend class DynamicMetadataMapIterator; }; +/** + * Lua wrapper for a network filter dynamic metadata. + */ +class ConnectionDynamicMetadataMapWrapper + : public Filters::Common::Lua::BaseLuaObject { +public: + ConnectionDynamicMetadataMapWrapper(ConnectionStreamInfoWrapper& parent) : parent_{parent} {} + + static ExportedFunctions exportedFunctions() { + return {{"get", static_luaConnectionDynamicMetadataGet}, + {"__pairs", static_luaConnectionDynamicMetadataPairs}}; + } + +private: + /** + * Get a metadata value from the map. + * @param 1 (string): filter name. + * @return value if found or nil. + */ + DECLARE_LUA_FUNCTION(ConnectionDynamicMetadataMapWrapper, luaConnectionDynamicMetadataGet); + + /** + * Implementation of the __pairs meta method so a dynamic metadata wrapper can be iterated over + * using pairs(). + */ + DECLARE_LUA_FUNCTION(ConnectionDynamicMetadataMapWrapper, luaConnectionDynamicMetadataPairs); + + // Envoy::Lua::BaseLuaObject + void onMarkDead() override { + // Iterators do not survive yields. + iterator_.reset(); + } + + // To get reference to parent's (StreamInfoWrapper) stream info member. + const StreamInfo::StreamInfo& streamInfo(); + + ConnectionStreamInfoWrapper& parent_; + Filters::Common::Lua::LuaDeathRef iterator_; + + friend class ConnectionDynamicMetadataMapIterator; +}; + /** * Lua wrapper for a stream info. */ @@ -257,6 +319,35 @@ class StreamInfoWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + ConnectionStreamInfoWrapper(const StreamInfo::StreamInfo& connection_stream_info) + : connection_stream_info_{connection_stream_info} {} + static ExportedFunctions exportedFunctions() { + return {{"dynamicMetadata", static_luaConnectionDynamicMetadata}}; + } + +private: + /** + * Get reference to stream info dynamic metadata object. + * @return ConnectionDynamicMetadataMapWrapper representation of StreamInfo dynamic metadata. + */ + DECLARE_LUA_FUNCTION(ConnectionStreamInfoWrapper, luaConnectionDynamicMetadata); + + // Envoy::Lua::BaseLuaObject + void onMarkDead() override { connection_dynamic_metadata_wrapper_.reset(); } + + const StreamInfo::StreamInfo& connection_stream_info_; + Filters::Common::Lua::LuaDeathRef + connection_dynamic_metadata_wrapper_; + + friend class ConnectionDynamicMetadataMapWrapper; +}; + /** * Lua wrapper for key for accessing the imported public keys. */ diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index 9483714f223a..4444ff9a6f07 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -1800,6 +1800,55 @@ TEST_F(LuaHttpFilterTest, GetRequestedServerName) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); } +// Verify that network connection level streamInfo():dynamicMetadata() could be accessed using LUA. +TEST_F(LuaHttpFilterTest, GetConnectionDynamicMetadata) { + const std::string SCRIPT{R"EOF( + function envoy_on_request(request_handle) + local cx_metadata = request_handle:connectionStreamInfo():dynamicMetadata() + local filters_count = 0 + for filter_name, _ in pairs(cx_metadata) do + filters_count = filters_count + 1 + end + request_handle:logTrace('Filters Count: ' .. filters_count) + + local pp_metadata_entries = cx_metadata:get("envoy.proxy_protocol") + for key, value in pairs(pp_metadata_entries) do + request_handle:logTrace('Key: ' .. key .. ', Value: ' .. value) + end + + local lb_version = cx_metadata:get("envoy.lb")["version"] + request_handle:logTrace('Key: version, Value: ' .. lb_version) + end + )EOF"}; + + // Proxy Protocol Filter Metadata + ProtobufWkt::Value tlv_ea_value; + tlv_ea_value.set_string_value("vpce-064c279a4001a055f"); + ProtobufWkt::Struct proxy_protocol_metadata; + proxy_protocol_metadata.mutable_fields()->insert({"tlv_ea", tlv_ea_value}); + (*stream_info_.metadata_.mutable_filter_metadata())["envoy.proxy_protocol"] = + proxy_protocol_metadata; + + // LB Filter Metadata + ProtobufWkt::Value lb_version_value; + lb_version_value.set_string_value("v1.0"); + ProtobufWkt::Struct lb_metadata; + lb_metadata.mutable_fields()->insert({"version", lb_version_value}); + (*stream_info_.metadata_.mutable_filter_metadata())["envoy.lb"] = lb_metadata; + + InSequence s; + setup(SCRIPT); + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_CALL(decoder_callbacks_, connection()).WillOnce(Return(&connection_)); + EXPECT_CALL(Const(connection_), streamInfo()).WillOnce(ReturnRef(stream_info_)); + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("Filters Count: 2"))); + EXPECT_CALL(*filter_, + scriptLog(spdlog::level::trace, StrEq("Key: tlv_ea, Value: vpce-064c279a4001a055f"))); + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("Key: version, Value: v1.0"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + // Verify that binary values could also be extracted from dynamicMetadata() in LUA filter. TEST_F(LuaHttpFilterTest, GetDynamicMetadataBinaryData) { const std::string SCRIPT{R"EOF(