diff --git a/CODEOWNERS b/CODEOWNERS index 7e96bea727433..5ff830804e613 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -201,4 +201,5 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp /contrib/mysql_proxy/ @rshriram @venilnoronha /contrib/postgres_proxy/ @fabriziomello @cpakulski @dio /contrib/sxg/ @cpapazian @rgs1 @alyssawilk +/contrib/sip_proxy/ @durd07 @nearbyfly @dorisd0102 /contrib/cryptomb/ @rojkov @ipuustin diff --git a/api/BUILD b/api/BUILD index 869e03ceaaa89..5f3637f0957bb 100644 --- a/api/BUILD +++ b/api/BUILD @@ -64,6 +64,8 @@ proto_library( "//contrib/envoy/extensions/filters/network/mysql_proxy/v3:pkg", "//contrib/envoy/extensions/filters/network/postgres_proxy/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/rocketmq_proxy/v3:pkg", + "//contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha:pkg", + "//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha:pkg", "//envoy/admin/v3:pkg", "//envoy/config/accesslog/v3:pkg", diff --git a/api/contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/BUILD b/api/contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/router.proto b/api/contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/router.proto new file mode 100644 index 0000000000000..4b7accacf406f --- /dev/null +++ b/api/contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/router.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package envoy.extensions.filters.network.sip_proxy.router.v3alpha; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.network.sip_proxy.router.v3alpha"; +option java_outer_classname = "RouterProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Router] +// [#extension: envoy.filters.sip.router] + +message Router { +} diff --git a/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/BUILD b/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/BUILD new file mode 100644 index 0000000000000..ee92fb652582e --- /dev/null +++ b/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/route.proto b/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/route.proto new file mode 100644 index 0000000000000..03c17a8ede82e --- /dev/null +++ b/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/route.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package envoy.extensions.filters.network.sip_proxy.v3alpha; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.network.sip_proxy.v3alpha"; +option java_outer_classname = "RouteProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Sip Proxy Route Configuration] + +message RouteConfiguration { + // The name of the route configuration. Reserved for future use in asynchronous route discovery. + string name = 1; + + // The list of routes that will be matched, in order, against incoming requests. The first route + // that matches will be used. + repeated Route routes = 2; +} + +message Route { + // Route matching parameters. + RouteMatch match = 1 [(validate.rules).message = {required: true}]; + + // Route request to some upstream cluster. + RouteAction route = 2 [(validate.rules).message = {required: true}]; +} + +message RouteMatch { + oneof match_specifier { + option (validate.required) = true; + + // The domain from Request URI or Route Header. + string domain = 1; + } +} + +message RouteAction { + oneof cluster_specifier { + option (validate.required) = true; + + // Indicates a single upstream cluster to which the request should be routed + // to. + string cluster = 1 [(validate.rules).string = {min_len: 1}]; + } +} diff --git a/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.proto b/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.proto new file mode 100644 index 0000000000000..380ee714f40c2 --- /dev/null +++ b/api/contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.proto @@ -0,0 +1,108 @@ +syntax = "proto3"; + +package envoy.extensions.filters.network.sip_proxy.v3alpha; + +import "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/route.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.network.sip_proxy.v3alpha"; +option java_outer_classname = "SipProxyProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Sip Proxy] +// [#extension: envoy.filters.network.sip_proxy] + +message SipProxy { + message SipSettings { + // transaction timeout timer [Timer B] unit is milliseconds, default value 64*T1. + // + // Session Initiation Protocol (SIP) timer summary + // + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer | Default value | Section | Meaning | + // +=========+=========================+==========+==============================================================================+ + // | T1 | 500 ms | 17.1.1.1 | Round-trip time (RTT) estimate | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | T2 | 4 sec | 17.1.2.2 | Maximum re-transmission interval for non-INVITE requests and INVITE responses| + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | T4 | 5 sec | 17.1.2.2 | Maximum duration that a message can remain in the network | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer A | initially T1 | 17.1.1.2 | INVITE request re-transmission interval, for UDP only | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer B | 64*T1 | 17.1.1.2 | INVITE transaction timeout timer | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer D | > 32 sec. for UDP | 17.1.1.2 | Wait time for response re-transmissions | + // | | 0 sec. for TCP and SCTP | | | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer E | initially T1 | 17.1.2.2 | Non-INVITE request re-transmission interval, UDP only | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer F | 64*T1 | 17.1.2.2 | Non-INVITE transaction timeout timer | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer G | initially T1 | 17.2.1 | INVITE response re-transmission interval | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer H | 64*T1 | 17.2.1 | Wait time for ACK receipt | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer I | T4 for UDP | 17.2.1 | Wait time for ACK re-transmissions | + // | | 0 sec. for TCP and SCTP | | | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer J | 64*T1 for UDP | 17.2.2 | Wait time for re-transmissions of non-INVITE requests | + // | | 0 sec. for TCP and SCTP | | | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + // | Timer K | T4 for UDP | 17.1.2.2 | Wait time for response re-transmissions | + // | | 0 sec. for TCP and SCTP | | | + // +---------+-------------------------+----------+------------------------------------------------------------------------------+ + google.protobuf.Duration transaction_timeout = 1; + + // own domain name + string own_domain = 2; + + // points to domain match with own_domain + string domain_match_parameter_name = 3; + } + + // The human readable prefix to use when emitting statistics. + string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; + + // The route table for the connection manager is static and is specified in this property. + RouteConfiguration route_config = 2; + + // A list of individual Sip filters that make up the filter chain for requests made to the + // Sip proxy. Order matters as the filters are processed sequentially. For backwards + // compatibility, if no sip_filters are specified, a default Sip router filter + // (`envoy.filters.sip.router`) is used. + // [#extension-category: envoy.sip_proxy.filters] + repeated SipFilter sip_filters = 3; + + SipSettings settings = 4; +} + +// SipFilter configures a Sip filter. +message SipFilter { + // The name of the filter to instantiate. The name must match a supported + // filter. The built-in filters are: + // + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // Filter specific configuration which depends on the filter being instantiated. See the supported + // filters for further documentation. + oneof config_type { + google.protobuf.Any typed_config = 3; + } +} + +// SipProtocolOptions specifies Sip upstream protocol options. This object is used in +// :ref:`typed_extension_protocol_options`, +// keyed by the name `envoy.filters.network.sip_proxy`. +message SipProtocolOptions { + // All sip messages in one dialog should go to the same endpoint. + bool session_affinity = 1; + + // The Register with Authorization header should go to the same endpoint which send out the 401 Unauthorized. + bool registration_affinity = 2; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index b8a813665f36d..362c9789e9e7c 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -16,6 +16,8 @@ proto_library( "//contrib/envoy/extensions/filters/network/mysql_proxy/v3:pkg", "//contrib/envoy/extensions/filters/network/postgres_proxy/v3alpha:pkg", "//contrib/envoy/extensions/filters/network/rocketmq_proxy/v3:pkg", + "//contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha:pkg", + "//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg", "//contrib/envoy/extensions/private_key_providers/cryptomb/v3alpha:pkg", "//envoy/admin/v3:pkg", "//envoy/config/accesslog/v3:pkg", diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 614c0e0523f71..3a9987a910e49 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -17,6 +17,13 @@ CONTRIB_EXTENSIONS = { "envoy.filters.network.postgres_proxy": "//contrib/postgres_proxy/filters/network/source:config", "envoy.filters.network.rocketmq_proxy": "//contrib/rocketmq_proxy/filters/network/source:config", + # + # Sip proxy + # + + "envoy.filters.network.sip_proxy": "//contrib/sip_proxy/filters/network/source:config", + "envoy.filters.sip.router": "//contrib/sip_proxy/filters/network/source/router:config", + # # Private key providers # diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index 50d112674d392..215a7936f0604 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -33,6 +33,16 @@ envoy.filters.network.postgres_proxy: - envoy.filters.network security_posture: requires_trusted_downstream_and_upstream status: stable +envoy.filters.network.sip_proxy: + categories: + - envoy.filters.network + security_posture: requires_trusted_downstream_and_upstream + status: alpha +envoy.filters.sip.router: + categories: + - envoy.sip_proxy.filters + security_posture: requires_trusted_downstream_and_upstream + status: alpha envoy.tls.key_providers.cryptomb: categories: - envoy.tls.key_providers diff --git a/contrib/sip_proxy/filters/network/source/BUILD b/contrib/sip_proxy/filters/network/source/BUILD new file mode 100644 index 0000000000000..90e7739507d48 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/BUILD @@ -0,0 +1,164 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "app_exception_lib", + srcs = ["app_exception_impl.cc"], + hdrs = ["app_exception_impl.h"], + deps = [ + ":protocol_interface", + ":sip_lib", + "//envoy/buffer:buffer_interface", + ], +) + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":app_exception_lib", + ":conn_manager_lib", + ":decoder_lib", + ":protocol_interface", + "//contrib/sip_proxy/filters/network/source/filters:filter_config_interface", + "//contrib/sip_proxy/filters/network/source/filters:well_known_names", + "//contrib/sip_proxy/filters/network/source/router:router_lib", + "//envoy/registry", + "//source/common/config:utility_lib", + "//source/extensions/filters/network/common:factory_base_lib", + "@envoy_api//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "conn_manager_lib", + srcs = ["conn_manager.cc"], + hdrs = ["conn_manager.h"], + external_deps = ["abseil_any"], + deps = [ + ":app_exception_lib", + ":decoder_lib", + ":encoder_lib", + ":protocol_interface", + ":stats_lib", + "//contrib/sip_proxy/filters/network/source/router:router_interface", + "//envoy/event:deferred_deletable", + "//envoy/event:dispatcher_interface", + "//envoy/network:connection_interface", + "//envoy/network:filter_interface", + "//envoy/stats:stats_interface", + "//envoy/stats:timespan_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/common:linked_object", + "//source/common/common:logger_lib", + "//source/common/network:filter_lib", + "//source/common/stats:timespan_lib", + "//source/common/stream_info:stream_info_lib", + ], +) + +envoy_cc_library( + name = "decoder_events_lib", + hdrs = ["decoder_events.h"], + deps = [ + ":metadata_lib", + ":sip_lib", + ], +) + +envoy_cc_library( + name = "decoder_lib", + srcs = ["decoder.cc"], + hdrs = ["decoder.h"], + deps = [ + ":app_exception_lib", + ":protocol_interface", + ":stats_lib", + "//contrib/sip_proxy/filters/network/source/filters:filter_interface", + "//source/common/buffer:buffer_lib", + ], +) + +envoy_cc_library( + name = "encoder_lib", + srcs = ["encoder.cc"], + hdrs = ["encoder.h"], + deps = [ + ":app_exception_lib", + ":protocol_interface", + ":stats_lib", + "//contrib/sip_proxy/filters/network/source/filters:filter_interface", + "//source/common/buffer:buffer_lib", + ], +) + +envoy_cc_library( + name = "metadata_lib", + hdrs = [ + "metadata.h", + "operation.h", + ], + external_deps = ["abseil_optional"], + deps = [ + ":sip_lib", + "//envoy/buffer:buffer_interface", + "//source/common/common:macros", + "//source/common/http:header_map_lib", + ], +) + +envoy_cc_library( + name = "protocol_interface", + hdrs = [ + "protocol.h", + ], + external_deps = ["abseil_optional"], + deps = [ + ":conn_state_lib", + ":decoder_events_lib", + ":metadata_lib", + ":sip_lib", + "//envoy/buffer:buffer_interface", + "//envoy/config:typed_config_interface", + "//envoy/registry", + "//source/common/common:assert_lib", + "//source/common/config:utility_lib", + "//source/common/singleton:const_singleton", + ], +) + +envoy_cc_library( + name = "stats_lib", + hdrs = ["stats.h"], + deps = [ + "//envoy/stats:stats_interface", + "//envoy/stats:stats_macros", + ], +) + +envoy_cc_library( + name = "conn_state_lib", + hdrs = ["conn_state.h"], + deps = [ + "//envoy/tcp:conn_pool_interface", + ], +) + +envoy_cc_library( + name = "sip_lib", + hdrs = ["sip.h"], + deps = [ + "//source/common/common:assert_lib", + "//source/common/singleton:const_singleton", + ], +) diff --git a/contrib/sip_proxy/filters/network/source/app_exception_impl.cc b/contrib/sip_proxy/filters/network/source/app_exception_impl.cc new file mode 100644 index 0000000000000..e2dcd4953434f --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/app_exception_impl.cc @@ -0,0 +1,21 @@ +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +DirectResponse::ResponseType AppException::encode(MessageMetadata& metadata, + Buffer::Instance& buffer) const { + (void)metadata; + (void)buffer; + + // TODO + + return DirectResponse::ResponseType::Exception; +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/app_exception_impl.h b/contrib/sip_proxy/filters/network/source/app_exception_impl.h new file mode 100644 index 0000000000000..9d2bc76aa6a46 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/app_exception_impl.h @@ -0,0 +1,27 @@ +#pragma once + +#include "envoy/common/exception.h" + +#include "contrib/sip_proxy/filters/network/source/metadata.h" +#include "contrib/sip_proxy/filters/network/source/protocol.h" +#include "contrib/sip_proxy/filters/network/source/sip.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +struct AppException : public EnvoyException, public DirectResponse { + AppException(AppExceptionType type, const std::string& what) + : EnvoyException(what), type_(type) {} + AppException(const AppException& ex) : EnvoyException(ex.what()), type_(ex.type_) {} + + ResponseType encode(MessageMetadata& metadata, Buffer::Instance& buffer) const override; + + const AppExceptionType type_; +}; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/config.cc b/contrib/sip_proxy/filters/network/source/config.cc new file mode 100644 index 0000000000000..f8247d8729db6 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/config.cc @@ -0,0 +1,135 @@ +#include "contrib/sip_proxy/filters/network/source/config.h" + +#include + +#include "envoy/network/connection.h" +#include "envoy/registry/registry.h" + +#include "source/common/config/utility.h" + +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.validate.h" +#include "contrib/sip_proxy/filters/network/source/decoder.h" +#include "contrib/sip_proxy/filters/network/source/filters/filter_config.h" +#include "contrib/sip_proxy/filters/network/source/filters/well_known_names.h" +#include "contrib/sip_proxy/filters/network/source/router/router_impl.h" +#include "contrib/sip_proxy/filters/network/source/stats.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +namespace { +inline void +addUniqueClusters(absl::flat_hash_set& clusters, + const envoy::extensions::filters::network::sip_proxy::v3alpha::Route& route) { + clusters.emplace(route.route().cluster()); +} +} // namespace + +ProtocolOptionsConfigImpl::ProtocolOptionsConfigImpl( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipProtocolOptions& config) + : session_affinity_(config.session_affinity()), + registration_affinity_(config.registration_affinity()) {} + +bool ProtocolOptionsConfigImpl::sessionAffinity() const { return session_affinity_; } +bool ProtocolOptionsConfigImpl::registrationAffinity() const { return registration_affinity_; } + +Network::FilterFactoryCb SipProxyFilterConfigFactory::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy& proto_config, + Server::Configuration::FactoryContext& context) { + std::shared_ptr filter_config(new ConfigImpl(proto_config, context)); + + absl::flat_hash_set unique_clusters; + for (auto& route : proto_config.route_config().routes()) { + addUniqueClusters(unique_clusters, route); + } + + /** + * ConnPool::InstanceImpl contains ThreadLocalObject ThreadLocalPool which only can be + * instantiated on main thread. so construct ConnPool::InstanceImpl here. + */ + auto transaction_infos = std::make_shared(); + for (auto& cluster : unique_clusters) { + Stats::ScopePtr stats_scope = + context.scope().createScope(fmt::format("cluster.{}.sip_cluster", cluster)); + auto transaction_info_ptr = std::make_shared( + cluster, context.threadLocal(), + static_cast( + PROTOBUF_GET_MS_OR_DEFAULT(proto_config.settings(), transaction_timeout, 32000)), + proto_config.settings().own_domain(), + proto_config.settings().domain_match_parameter_name()); + transaction_info_ptr->init(); + transaction_infos->emplace(cluster, transaction_info_ptr); + } + + return + [filter_config, &context, transaction_infos](Network::FilterManager& filter_manager) -> void { + filter_manager.addReadFilter(std::make_shared( + *filter_config, context.api().randomGenerator(), + context.mainThreadDispatcher().timeSource(), transaction_infos)); + }; +} + +/** + * Static registration for the sip filter. @see RegisterFactory. + */ +REGISTER_FACTORY(SipProxyFilterConfigFactory, + Server::Configuration::NamedNetworkFilterConfigFactory); + +ConfigImpl::ConfigImpl( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy& config, + Server::Configuration::FactoryContext& context) + : context_(context), stats_prefix_(fmt::format("sip.{}.", config.stat_prefix())), + stats_(SipFilterStats::generateStats(stats_prefix_, context_.scope())), + route_matcher_(new Router::RouteMatcher(config.route_config())), + settings_(std::make_shared( + static_cast( + PROTOBUF_GET_MS_OR_DEFAULT(config.settings(), transaction_timeout, 32000)), + config.settings().own_domain(), config.settings().domain_match_parameter_name())) { + + if (config.sip_filters().empty()) { + ENVOY_LOG(debug, "using default router filter"); + + envoy::extensions::filters::network::sip_proxy::v3alpha::SipFilter router; + router.set_name(SipFilters::SipFilterNames::get().ROUTER); + processFilter(router); + } else { + for (const auto& filter : config.sip_filters()) { + processFilter(filter); + } + } +} + +void ConfigImpl::createFilterChain(SipFilters::FilterChainFactoryCallbacks& callbacks) { + for (const SipFilters::FilterFactoryCb& factory : filter_factories_) { + factory(callbacks); + } +} + +void ConfigImpl::processFilter( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipFilter& proto_config) { + const std::string& string_name = proto_config.name(); + + ENVOY_LOG(debug, " sip filter #{}", filter_factories_.size()); + ENVOY_LOG(debug, " name: {}", string_name); + ENVOY_LOG(debug, " config: {}", + MessageUtil::getJsonStringFromMessageOrError( + static_cast(proto_config.typed_config()))); + auto& factory = + Envoy::Config::Utility::getAndCheckFactory( + proto_config); + + ProtobufTypes::MessagePtr message = Envoy::Config::Utility::translateAnyToFactoryConfig( + proto_config.typed_config(), context_.messageValidationVisitor(), factory); + SipFilters::FilterFactoryCb callback = + factory.createFilterFactoryFromProto(*message, stats_prefix_, context_); + + filter_factories_.push_back(callback); +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/config.h b/contrib/sip_proxy/filters/network/source/config.h new file mode 100644 index 0000000000000..f741bf00bcb17 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/config.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +#include "source/extensions/filters/network/common/factory_base.h" + +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.validate.h" +#include "contrib/sip_proxy/filters/network/source/conn_manager.h" +#include "contrib/sip_proxy/filters/network/source/filters/filter.h" +#include "contrib/sip_proxy/filters/network/source/filters/well_known_names.h" +#include "contrib/sip_proxy/filters/network/source/router/router_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +/** + * Provides Sip-specific cluster options. + */ +class ProtocolOptionsConfigImpl : public ProtocolOptionsConfig { +public: + ProtocolOptionsConfigImpl( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipProtocolOptions& + proto_config); + + bool sessionAffinity() const override; + bool registrationAffinity() const override; + +private: + bool session_affinity_; + bool registration_affinity_; +}; + +/** + * Config registration for the sip proxy filter. @see NamedNetworkFilterConfigFactory. + */ +class SipProxyFilterConfigFactory + : public Common::FactoryBase< + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy, + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProtocolOptions> { +public: + SipProxyFilterConfigFactory() : FactoryBase(SipProxy, true) {} + +private: + Network::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy& proto_config, + Server::Configuration::FactoryContext& context) override; + Upstream::ProtocolOptionsConfigConstSharedPtr createProtocolOptionsTyped( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipProtocolOptions& + proto_config, + Server::Configuration::ProtocolOptionsFactoryContext&) override { + return std::make_shared(proto_config); + } +}; + +class ConfigImpl : public Config, + public Router::Config, + public SipFilters::FilterChainFactory, + Logger::Loggable { +public: + ConfigImpl(const envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy& config, + Server::Configuration::FactoryContext& context); + + // SipFilters::FilterChainFactory + void createFilterChain(SipFilters::FilterChainFactoryCallbacks& callbacks) override; + + // Router::Config + Router::RouteConstSharedPtr route(MessageMetadata& metadata) const override { + return route_matcher_->route(metadata); + } + + // Config + SipFilterStats& stats() override { return stats_; } + SipFilters::FilterChainFactory& filterFactory() override { return *this; } + Router::Config& routerConfig() override { return *this; } + std::shared_ptr settings() override { return settings_; } + + // Settings +private: + void processFilter( + const envoy::extensions::filters::network::sip_proxy::v3alpha::SipFilter& proto_config); + + Server::Configuration::FactoryContext& context_; + const std::string stats_prefix_; + SipFilterStats stats_; + std::unique_ptr route_matcher_; + + std::list filter_factories_; + + std::shared_ptr settings_; +}; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/conn_manager.cc b/contrib/sip_proxy/filters/network/source/conn_manager.cc new file mode 100644 index 0000000000000..4f28f3f4bb545 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/conn_manager.cc @@ -0,0 +1,338 @@ +#include "contrib/sip_proxy/filters/network/source/conn_manager.h" + +#include "envoy/common/exception.h" +#include "envoy/event/dispatcher.h" + +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" +#include "contrib/sip_proxy/filters/network/source/encoder.h" +#include "contrib/sip_proxy/filters/network/source/protocol.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +ConnectionManager::ConnectionManager(Config& config, Random::RandomGenerator& random_generator, + TimeSource& time_source, + std::shared_ptr transaction_infos) + : config_(config), stats_(config_.stats()), decoder_(std::make_unique(*this)), + random_generator_(random_generator), time_source_(time_source), + transaction_infos_(transaction_infos) {} + +ConnectionManager::~ConnectionManager() = default; + +Network::FilterStatus ConnectionManager::onData(Buffer::Instance& data, bool end_stream) { + ENVOY_LOG(trace, "ConnectionManager received data {}\n{}\n", data.length(), data.toString()); + request_buffer_.move(data); + dispatch(); + + if (end_stream) { + ENVOY_CONN_LOG(info, "downstream half-closed", read_callbacks_->connection()); + + resetAllTrans(false); + read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + } + + return Network::FilterStatus::StopIteration; +} + +void ConnectionManager::dispatch() { decoder_->onData(request_buffer_); } + +void ConnectionManager::sendLocalReply(MessageMetadata& metadata, const DirectResponse& response, + bool end_stream) { + if (read_callbacks_->connection().state() == Network::Connection::State::Closed) { + return; + } + + Buffer::OwnedImpl buffer; + const DirectResponse::ResponseType result = response.encode(metadata, buffer); + + Buffer::OwnedImpl response_buffer; + + metadata.setEP(getLocalIp()); + std::shared_ptr encoder = std::make_shared(); + encoder->encode(std::make_shared(metadata), response_buffer); + + read_callbacks_->connection().write(response_buffer, end_stream); + if (end_stream) { + read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + } + + switch (result) { + case DirectResponse::ResponseType::SuccessReply: + stats_.response_success_.inc(); + break; + case DirectResponse::ResponseType::ErrorReply: + stats_.response_error_.inc(); + break; + case DirectResponse::ResponseType::Exception: + stats_.response_exception_.inc(); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +void ConnectionManager::doDeferredTransDestroy(ConnectionManager::ActiveTrans& trans) { + read_callbacks_->connection().dispatcher().deferredDelete( + std::move(transactions_.at(trans.transactionId()))); + transactions_.erase(trans.transactionId()); +} + +void ConnectionManager::resetAllTrans(bool local_reset) { + ENVOY_LOG(info, "active_trans to be deleted {}", transactions_.size()); + for (auto it = transactions_.cbegin(); it != transactions_.cend();) { + if (local_reset) { + ENVOY_CONN_LOG(debug, "local close with active request", read_callbacks_->connection()); + stats_.cx_destroy_local_with_active_rq_.inc(); + } else { + ENVOY_CONN_LOG(debug, "remote close with active request", read_callbacks_->connection()); + stats_.cx_destroy_remote_with_active_rq_.inc(); + } + + (it++)->second->onReset(); + } +} + +void ConnectionManager::initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) { + read_callbacks_ = &callbacks; + + read_callbacks_->connection().addConnectionCallbacks(*this); + read_callbacks_->connection().enableHalfClose(true); +} + +void ConnectionManager::onEvent(Network::ConnectionEvent event) { + ENVOY_CONN_LOG(info, "received event {}", read_callbacks_->connection(), event); + resetAllTrans(event == Network::ConnectionEvent::LocalClose); +} + +DecoderEventHandler& ConnectionManager::newDecoderEventHandler(MessageMetadataSharedPtr metadata) { + ENVOY_LOG(trace, "new decoder filter"); + std::string&& k = std::string(metadata->transactionId().value()); + if (metadata->methodType() == MethodType::Ack) { + if (transactions_.find(k) != transactions_.end()) { + // ACK_4XX + return *transactions_.at(k); + } + } + + ActiveTransPtr new_trans = std::make_unique(*this, metadata); + new_trans->createFilterChain(); + transactions_.emplace(k, std::move(new_trans)); + + return *transactions_.at(k); +} + +bool ConnectionManager::ResponseDecoder::onData(MessageMetadataSharedPtr metadata) { + metadata_ = metadata; + if (auto status = transportBegin(metadata_); status == FilterStatus::StopIteration) { + return true; + } + + if (auto status = messageBegin(metadata_); status == FilterStatus::StopIteration) { + return true; + } + + if (auto status = messageEnd(); status == FilterStatus::StopIteration) { + return true; + } + + if (auto status = transportEnd(); status == FilterStatus::StopIteration) { + return true; + } + + return true; +} + +FilterStatus ConnectionManager::ResponseDecoder::messageBegin(MessageMetadataSharedPtr metadata) { + UNREFERENCED_PARAMETER(metadata); + return FilterStatus::Continue; +} + +FilterStatus ConnectionManager::ResponseDecoder::messageEnd() { return FilterStatus::Continue; } + +FilterStatus ConnectionManager::ResponseDecoder::transportEnd() { + ASSERT(metadata_ != nullptr); + + ConnectionManager& cm = parent_.parent_; + + if (cm.read_callbacks_->connection().state() == Network::Connection::State::Closed) { + throw EnvoyException("downstream connection is closed"); + } + + Buffer::OwnedImpl buffer; + + metadata_->setEP(getLocalIp()); + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, buffer); + + ENVOY_LOG(trace, "send response {}\n{}", buffer.length(), buffer.toString()); + cm.read_callbacks_->connection().write(buffer, false); + + cm.stats_.response_.inc(); + + return FilterStatus::Continue; +} + +FilterStatus ConnectionManager::ActiveTrans::applyDecoderFilters(ActiveTransDecoderFilter* filter) { + ASSERT(filter_action_ != nullptr); + + if (!local_response_sent_) { + std::list::iterator entry; + if (!filter) { + entry = decoder_filters_.begin(); + } else { + entry = std::next(filter->entry()); + } + + for (; entry != decoder_filters_.end(); entry++) { + const FilterStatus status = filter_action_((*entry)->handle_.get()); + if (local_response_sent_) { + // The filter called sendLocalReply: stop processing filters and return + // FilterStatus::Continue irrespective of the current result. + break; + } + + if (status != FilterStatus::Continue) { + return status; + } + } + } + + filter_action_ = nullptr; + filter_context_.reset(); + + return FilterStatus::Continue; +} + +FilterStatus ConnectionManager::ActiveTrans::transportBegin(MessageMetadataSharedPtr metadata) { + filter_context_ = metadata; + filter_action_ = [this](DecoderEventHandler* filter) -> FilterStatus { + MessageMetadataSharedPtr metadata = absl::any_cast(filter_context_); + return filter->transportBegin(metadata); + }; + + return applyDecoderFilters(nullptr); +} + +FilterStatus ConnectionManager::ActiveTrans::transportEnd() { + ASSERT(metadata_ != nullptr); + parent_.stats_.request_.inc(); + + FilterStatus status; + filter_action_ = [](DecoderEventHandler* filter) -> FilterStatus { + return filter->transportEnd(); + }; + + status = applyDecoderFilters(nullptr); + if (status == FilterStatus::StopIteration) { + return status; + } + + finalizeRequest(); + + return status; +} + +void ConnectionManager::ActiveTrans::finalizeRequest() {} + +FilterStatus ConnectionManager::ActiveTrans::messageBegin(MessageMetadataSharedPtr metadata) { + metadata_ = metadata; + filter_context_ = metadata; + filter_action_ = [this](DecoderEventHandler* filter) -> FilterStatus { + MessageMetadataSharedPtr metadata = absl::any_cast(filter_context_); + return filter->messageBegin(metadata); + }; + + return applyDecoderFilters(nullptr); +} + +FilterStatus ConnectionManager::ActiveTrans::messageEnd() { + filter_action_ = [](DecoderEventHandler* filter) -> FilterStatus { return filter->messageEnd(); }; + return applyDecoderFilters(nullptr); +} + +void ConnectionManager::ActiveTrans::createFilterChain() { + parent_.config_.filterFactory().createFilterChain(*this); +} + +void ConnectionManager::ActiveTrans::onReset() { parent_.doDeferredTransDestroy(*this); } + +void ConnectionManager::ActiveTrans::onError(const std::string& what) { + if (metadata_) { + sendLocalReply(AppException(AppExceptionType::ProtocolError, what), true); + return; + } + + parent_.doDeferredTransDestroy(*this); + parent_.read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); +} + +const Network::Connection* ConnectionManager::ActiveTrans::connection() const { + return &parent_.read_callbacks_->connection(); +} + +Router::RouteConstSharedPtr ConnectionManager::ActiveTrans::route() { + if (!cached_route_) { + if (metadata_ != nullptr) { + Router::RouteConstSharedPtr route = parent_.config_.routerConfig().route(*metadata_); + cached_route_ = std::move(route); + } else { + cached_route_ = nullptr; + } + } + + return cached_route_.value(); +} + +void ConnectionManager::ActiveTrans::sendLocalReply(const DirectResponse& response, + bool end_stream) { + parent_.sendLocalReply(*metadata_, response, end_stream); + + if (end_stream) { + return; + } + + // Consume any remaining request data from the downstream. + local_response_sent_ = true; +} + +void ConnectionManager::ActiveTrans::startUpstreamResponse() { + response_decoder_ = std::make_unique(*this); +} + +SipFilters::ResponseStatus +ConnectionManager::ActiveTrans::upstreamData(MessageMetadataSharedPtr metadata) { + ASSERT(response_decoder_ != nullptr); + + try { + if (response_decoder_->onData(metadata)) { + // Completed upstream response. + // parent_.doDeferredRpcDestroy(*this); + return SipFilters::ResponseStatus::Complete; + } + return SipFilters::ResponseStatus::MoreData; + } catch (const AppException& ex) { + ENVOY_LOG(error, "sip response application error: {}", ex.what()); + // parent_.stats_.response_decoding_error_.inc(); + + sendLocalReply(ex, true); + return SipFilters::ResponseStatus::Reset; + } catch (const EnvoyException& ex) { + ENVOY_CONN_LOG(error, "sip response error: {}", parent_.read_callbacks_->connection(), + ex.what()); + // parent_.stats_.response_decoding_error_.inc(); + + onError(ex.what()); + return SipFilters::ResponseStatus::Reset; + } +} + +void ConnectionManager::ActiveTrans::resetDownstreamConnection() { + parent_.read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/conn_manager.h b/contrib/sip_proxy/filters/network/source/conn_manager.h new file mode 100644 index 0000000000000..212efe605ff0b --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/conn_manager.h @@ -0,0 +1,282 @@ +#pragma once + +#include "envoy/common/pure.h" +#include "envoy/common/random_generator.h" +#include "envoy/event/deferred_deletable.h" +#include "envoy/network/connection.h" +#include "envoy/network/filter.h" +#include "envoy/stats/timespan.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/linked_object.h" +#include "source/common/common/logger.h" +#include "source/common/stats/timespan_impl.h" +#include "source/common/stream_info/stream_info_impl.h" + +#include "absl/types/any.h" +#include "contrib/sip_proxy/filters/network/source/decoder.h" +#include "contrib/sip_proxy/filters/network/source/filters/filter.h" +#include "contrib/sip_proxy/filters/network/source/protocol.h" +#include "contrib/sip_proxy/filters/network/source/stats.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +/** + * Config is a configuration interface for ConnectionManager. + */ +class SipSettings; +class Config { +public: + virtual ~Config() = default; + + virtual SipFilters::FilterChainFactory& filterFactory() PURE; + virtual SipFilterStats& stats() PURE; + virtual Router::Config& routerConfig() PURE; + virtual std::shared_ptr settings() PURE; +}; + +/** + * Extends Upstream::ProtocolOptionsConfig with Sip-specific cluster options. + */ +class ProtocolOptionsConfig : public Upstream::ProtocolOptionsConfig { +public: + ~ProtocolOptionsConfig() override = default; + + virtual bool sessionAffinity() const PURE; + virtual bool registrationAffinity() const PURE; +}; + +/** + * ConnectionManager is a Network::Filter that will perform Sip request handling on a connection. + */ +class ConnectionManager : public Network::ReadFilter, + public Network::ConnectionCallbacks, + public DecoderCallbacks, + Logger::Loggable { +public: + ConnectionManager(Config& config, Random::RandomGenerator& random_generator, + TimeSource& time_system, + std::shared_ptr transaction_infos); + ~ConnectionManager() override; + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; + Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks&) override; + + // Network::ConnectionCallbacks + void onEvent(Network::ConnectionEvent) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + + // DecoderCallbacks + DecoderEventHandler& newDecoderEventHandler(MessageMetadataSharedPtr metadata) override; + + absl::string_view getLocalIp() override { + // should return local address ip + // But after ORIGINAL_DEST, the local address update to upstream local address + // So here get downstream remote IP, which should in same pod car with envoy + ENVOY_LOG(debug, "Local ip: {}", + read_callbacks_->connection() + .connectionInfoProvider() + .localAddress() + ->ip() + ->addressAsString()); + return read_callbacks_->connection() + .connectionInfoProvider() + .localAddress() + ->ip() + ->addressAsString(); + } + + std::string getOwnDomain() override { return config_.settings()->ownDomain(); } + + std::string getDomainMatchParamName() override { + return config_.settings()->domainMatchParamName(); + } + +private: + friend class SipConnectionManagerTest; + struct ActiveTrans; + + struct ResponseDecoder : public DecoderCallbacks, public DecoderEventHandler { + ResponseDecoder(ActiveTrans& parent) : parent_(parent) {} + + bool onData(MessageMetadataSharedPtr metadata); + + // DecoderEventHandler + FilterStatus messageBegin(MessageMetadataSharedPtr metadata) override; + FilterStatus messageEnd() override; + FilterStatus transportBegin(MessageMetadataSharedPtr metadata) override { + UNREFERENCED_PARAMETER(metadata); + return FilterStatus::Continue; + } + FilterStatus transportEnd() override; + + // DecoderCallbacks + DecoderEventHandler& newDecoderEventHandler(MessageMetadataSharedPtr metadata) override { + UNREFERENCED_PARAMETER(metadata); + return *this; + } + + absl::string_view getLocalIp() override { + // should return local address ip + // But after ORIGINAL_DEST, the local address update to upstream local address + // So here get downstream remote IP, which should in same pod car with envoy + return parent_.parent_.getLocalIp(); + } + + std::string getOwnDomain() override { return parent_.parent_.getOwnDomain(); } + + std::string getDomainMatchParamName() override { + return parent_.parent_.getDomainMatchParamName(); + } + + ActiveTrans& parent_; + MessageMetadataSharedPtr metadata_; + }; + using ResponseDecoderPtr = std::unique_ptr; + + // Wraps a DecoderFilter and acts as the DecoderFilterCallbacks for the filter, enabling filter + // chain continuation. + struct ActiveTransDecoderFilter : public SipFilters::DecoderFilterCallbacks, + LinkedObject { + ActiveTransDecoderFilter(ActiveTrans& parent, SipFilters::DecoderFilterSharedPtr filter) + : parent_(parent), handle_(filter) {} + + // SipFilters::DecoderFilterCallbacks + uint64_t streamId() const override { return parent_.streamId(); } + std::string transactionId() const override { return parent_.transactionId(); } + const Network::Connection* connection() const override { return parent_.connection(); } + Router::RouteConstSharedPtr route() override { return parent_.route(); } + void sendLocalReply(const DirectResponse& response, bool end_stream) override { + parent_.sendLocalReply(response, end_stream); + } + void startUpstreamResponse() override { parent_.startUpstreamResponse(); } + SipFilters::ResponseStatus upstreamData(MessageMetadataSharedPtr metadata) override { + return parent_.upstreamData(metadata); + } + void resetDownstreamConnection() override { parent_.resetDownstreamConnection(); } + StreamInfo::StreamInfo& streamInfo() override { return parent_.streamInfo(); } + std::shared_ptr transactionInfos() override { + return parent_.transactionInfos(); + } + std::shared_ptr settings() override { return parent_.settings(); } + void onReset() override { return parent_.onReset(); } + + ActiveTrans& parent_; + SipFilters::DecoderFilterSharedPtr handle_; + }; + using ActiveTransDecoderFilterPtr = std::unique_ptr; + + // ActiveTrans tracks request/response pairs. + struct ActiveTrans : LinkedObject, + public Event::DeferredDeletable, + public DecoderEventHandler, + public SipFilters::DecoderFilterCallbacks, + public SipFilters::FilterChainFactoryCallbacks { + ActiveTrans(ConnectionManager& parent, MessageMetadataSharedPtr metadata) + : parent_(parent), request_timer_(new Stats::HistogramCompletableTimespanImpl( + parent_.stats_.request_time_ms_, parent_.time_source_)), + stream_id_(parent_.random_generator_.random()), + transaction_id_(metadata->transactionId().value()), + stream_info_(parent_.time_source_, + parent_.read_callbacks_->connection().connectionInfoProviderSharedPtr()), + metadata_(metadata), local_response_sent_(false) { + parent_.stats_.request_active_.inc(); + } + ~ActiveTrans() override { + request_timer_->complete(); + ENVOY_LOG(trace, "destruct activetrans {}", transaction_id_); + parent_.stats_.request_active_.dec(); + + for (auto& filter : decoder_filters_) { + filter->handle_->onDestroy(); + } + } + + // DecoderEventHandler + FilterStatus transportBegin(MessageMetadataSharedPtr metadata) override; + FilterStatus transportEnd() override; + FilterStatus messageBegin(MessageMetadataSharedPtr metadata) override; + FilterStatus messageEnd() override; + + // SipFilters::DecoderFilterCallbacks + uint64_t streamId() const override { return stream_id_; } + std::string transactionId() const override { return transaction_id_; } + const Network::Connection* connection() const override; + Router::RouteConstSharedPtr route() override; + void sendLocalReply(const DirectResponse& response, bool end_stream) override; + void startUpstreamResponse() override; + SipFilters::ResponseStatus upstreamData(MessageMetadataSharedPtr metadata) override; + void resetDownstreamConnection() override; + StreamInfo::StreamInfo& streamInfo() override { return stream_info_; } + + std::shared_ptr transactionInfos() override { + return parent_.transaction_infos_; + } + std::shared_ptr settings() override { return parent_.config_.settings(); } + void onReset() override; + + // Sip::FilterChainFactoryCallbacks + void addDecoderFilter(SipFilters::DecoderFilterSharedPtr filter) override { + ActiveTransDecoderFilterPtr wrapper = + std::make_unique(*this, filter); + filter->setDecoderFilterCallbacks(*wrapper); + LinkedList::moveIntoListBack(std::move(wrapper), decoder_filters_); + } + + FilterStatus applyDecoderFilters(ActiveTransDecoderFilter* filter); + void finalizeRequest(); + + void createFilterChain(); + void onError(const std::string& what); + + ConnectionManager& parent_; + Stats::TimespanPtr request_timer_; + uint64_t stream_id_; + std::string transaction_id_; + StreamInfo::StreamInfoImpl stream_info_; + MessageMetadataSharedPtr metadata_; + std::list decoder_filters_; + ResponseDecoderPtr response_decoder_; + absl::optional cached_route_; + std::function filter_action_; + + absl::any filter_context_; + bool local_response_sent_ : 1; + + /* Used by Router */ + std::shared_ptr transaction_infos_; + }; + + using ActiveTransPtr = std::unique_ptr; + + void dispatch(); + void sendLocalReply(MessageMetadata& metadata, const DirectResponse& response, bool end_stream); + void doDeferredTransDestroy(ActiveTrans& trans); + void resetAllTrans(bool local_reset); + + Config& config_; + SipFilterStats& stats_; + + Network::ReadFilterCallbacks* read_callbacks_{}; + + DecoderPtr decoder_; + absl::flat_hash_map transactions_; + Buffer::OwnedImpl request_buffer_; + Random::RandomGenerator& random_generator_; + TimeSource& time_source_; + + // This is used in Router, put here to pass to Router + std::shared_ptr transaction_infos_; + std::shared_ptr sip_settings_; +}; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/conn_state.h b/contrib/sip_proxy/filters/network/source/conn_state.h new file mode 100644 index 0000000000000..8b1ac41609719 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/conn_state.h @@ -0,0 +1,47 @@ +#pragma once + +#include "envoy/tcp/conn_pool.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +enum class ConnectionState { + NotConnected, + Connecting, + Connected, +}; + +/** Not used + * SipConnectionState tracks sip-related connection state for pooled + * connections. + */ +// class SipConnectionState : public Tcp::ConnectionPool::ConnectionState { +// public: +// SipConnectionState(SipProxy::ConnectionState state, int32_t initial_sequence_id = 0) +// : state_(state), next_sequence_id_(initial_sequence_id) {} +// +// /** +// * @return int32_t the next Sip sequence id to use for this connection. +// */ +// int32_t nextSequenceId() { +// if (next_sequence_id_ == std::numeric_limits::max()) { +// next_sequence_id_ = 0; +// return std::numeric_limits::max(); +// } +// +// return next_sequence_id_++; +// } +// +// SipProxy::ConnectionState state() { return state_; } +// +// private: +// SipProxy::ConnectionState state_; +// int32_t next_sequence_id_; +//}; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/decoder.cc b/contrib/sip_proxy/filters/network/source/decoder.cc new file mode 100644 index 0000000000000..28f2b661ade1d --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/decoder.cc @@ -0,0 +1,614 @@ +#include "contrib/sip_proxy/filters/network/source/decoder.h" + +#include "envoy/buffer/buffer.h" +#include "envoy/common/exception.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/assert.h" +#include "source/common/common/macros.h" + +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" +#include "re2/re2.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +DecoderStateMachine::DecoderStatus DecoderStateMachine::transportBegin() { + return {State::MessageBegin, handler_.transportBegin(metadata_)}; +} + +DecoderStateMachine::DecoderStatus DecoderStateMachine::messageBegin() { + return {State::MessageEnd, handler_.messageBegin(metadata_)}; +} + +DecoderStateMachine::DecoderStatus DecoderStateMachine::messageEnd() { + return {State::TransportEnd, handler_.messageEnd()}; +} + +DecoderStateMachine::DecoderStatus DecoderStateMachine::transportEnd() { + return {State::Done, handler_.transportEnd()}; +} + +DecoderStateMachine::DecoderStatus DecoderStateMachine::handleState() { + switch (state_) { + case State::TransportBegin: + return transportBegin(); + case State::MessageBegin: + return messageBegin(); + case State::MessageEnd: + return messageEnd(); + case State::TransportEnd: + return transportEnd(); + default: + /* test failed report "panic: not reached" if reach here */ + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +State DecoderStateMachine::run() { + while (state_ != State::Done) { + ENVOY_LOG(trace, "sip: state {}", StateNameValues::name(state_)); + + DecoderStatus s = handleState(); + + state_ = s.next_state_; + + ASSERT(s.filter_status_.has_value()); + if (s.filter_status_.value() == FilterStatus::StopIteration) { + return State::StopIteration; + } + } + + return state_; +} + +Decoder::Decoder(DecoderCallbacks& callbacks) : callbacks_(callbacks) {} + +void Decoder::complete() { + request_.reset(); + metadata_.reset(); + state_machine_ = nullptr; + start_new_message_ = true; + + current_header_ = HeaderType::TopLine; + raw_offset_ = 0; + + first_via_ = true; + first_route_ = true; +} + +FilterStatus Decoder::onData(Buffer::Instance& data) { + ENVOY_LOG(debug, "sip: {} bytes available", data.length()); + + reassemble(data); + return FilterStatus::StopIteration; +} + +int Decoder::reassemble(Buffer::Instance& data) { + // ENVOY_LOG(trace, "received --> {}\n{}", data.length(), data.toString()); + + Buffer::Instance& remaining_data = data; + + int ret = 0; + size_t clen = 0; // Content-Length value + size_t full_msg_len = 0; // Length of the entire message + + while (remaining_data.length() != 0) { + ssize_t content_pos = remaining_data.search("\n\r\n", strlen("\n\r\n"), 0); + if (content_pos != -1) { + // Get the Content-Length header value so that we can find + // out the full message length. + // + content_pos += 3; // move to the line after the CRLF line. + + ssize_t content_length_start = + remaining_data.search("Content-Length:", strlen("Content-Length:"), 0, content_pos); + if (content_length_start == -1) { + break; + } + + ssize_t content_length_end = remaining_data.search( + "\r\n", strlen("\r\n"), content_length_start + strlen("Content-Length:"), content_pos); + /* The "\n\r\n" is always included in remaining_data, so could not return -1 + if (content_length_end == -1) { + break; + } + */ + + char len[10]{}; // temporary storage + remaining_data.copyOut(content_length_start + strlen("Content-Length:"), + content_length_end - content_length_start - strlen("Content-Length:"), + len); + + clen = std::atoi(len); + + // Fail if Content-Length is less then zero + // + /* atoi return value >= 0, could not < 0 + if (clen < static_cast(0)) { + break; + } + */ + + full_msg_len = content_pos + clen; + } + + // Check for partial message received. + // + if ((full_msg_len == 0) || (full_msg_len > remaining_data.length())) { + break; + } else { + // We have a full SIP message; put it on the dispatch queue. + // + Buffer::OwnedImpl message{}; + message.move(remaining_data, full_msg_len); + /* status not used + auto status = onDataReady(message); + */ + onDataReady(message); + message.drain(message.length()); + full_msg_len = 0; + /* no handle for this if + if (status != FilterStatus::StopIteration) { + // break; + }*/ + } + } // End of while (remaining_data_len > 0) + + return ret; +} + +FilterStatus Decoder::onDataReady(Buffer::Instance& data) { + ENVOY_LOG(trace, "onDataReady {}\n{}", data.length(), data.toString()); + + metadata_ = std::make_shared(data.toString()); + + decode(); + + request_ = std::make_unique(callbacks_.newDecoderEventHandler(metadata_)); + state_machine_ = std::make_unique(metadata_, request_->handler_); + State rv = state_machine_->run(); + + if (rv == State::Done || rv == State::StopIteration) { + complete(); + } + + return FilterStatus::StopIteration; +} + +auto Decoder::sipHeaderType(absl::string_view sip_line) { + static std::map sip_header_type_map{ + {"Call-ID", HeaderType::CallId}, + {"Via", HeaderType::Via}, + {"To", HeaderType::To}, + {"From", HeaderType::From}, + {"Contact", HeaderType::Contact}, + {"Record-Route", HeaderType::RRoute}, + {"CSeq", HeaderType::Cseq}, + {"Route", HeaderType::Route}, + {"Path", HeaderType::Path}, + {"Event", HeaderType::Event}, + {"Service-Route", HeaderType::SRoute}, + {"WWW-Authenticate", HeaderType::WAuth}, + {"Authorization", HeaderType::Auth}, + {"", HeaderType::Other}}; + + auto header_type_str = sip_line.substr(0, sip_line.find_first_of(':')); + if (auto result = sip_header_type_map.find(header_type_str); + result != sip_header_type_map.end()) { + return std::tuple{ + result->second, sip_line.substr(sip_line.find_first_of(':') + strlen(": "))}; + } else { + return std::tuple{ + HeaderType::Other, sip_line.substr(sip_line.find_first_of(':') + strlen(": "))}; + } +} + +MsgType Decoder::sipMsgType(absl::string_view top_line) { + if (top_line.find("SIP/2.0 ") == absl::string_view::npos) { + return MsgType::Request; + } else { + return MsgType::Response; + } +} + +MethodType Decoder::sipMethod(absl::string_view top_line) { + if (top_line.find("INVITE") != absl::string_view::npos) { + return MethodType::Invite; + } else if (top_line.find("CANCEL") != absl::string_view::npos) { + return MethodType::Cancel; + } else if (top_line.find("REGISTER") != absl::string_view::npos) { + return MethodType::Register; + } else if (top_line.find("REFER") != absl::string_view::npos) { + return MethodType::Refer; + } else if (top_line.find("UPDATE") != absl::string_view::npos) { + return MethodType::Update; + } else if (top_line.find("SUBSCRIBE") != absl::string_view::npos) { + return MethodType::Subscribe; + } else if (top_line.find("NOTIFY") != absl::string_view::npos) { + return MethodType::Notify; + } else if (top_line.find("ACK") != absl::string_view::npos) { + return MethodType::Ack; + } else if (top_line.find("BYE") != absl::string_view::npos) { + return MethodType::Bye; + } else if (top_line.find("2.0 200") != absl::string_view::npos) { + return MethodType::Ok200; + } else if (top_line.find("2.0 4") != absl::string_view::npos) { + return MethodType::Failure4xx; + } else { + return MethodType::NullMethod; + } +} + +Decoder::HeaderHandler::HeaderHandler(MessageHandler& parent) + : parent_(parent), header_processors_{ + {HeaderType::Via, &HeaderHandler::processVia}, + {HeaderType::Route, &HeaderHandler::processRoute}, + {HeaderType::Contact, &HeaderHandler::processContact}, + {HeaderType::Cseq, &HeaderHandler::processCseq}, + {HeaderType::RRoute, &HeaderHandler::processRecordRoute}, + {HeaderType::SRoute, &HeaderHandler::processServiceRoute}, + {HeaderType::WAuth, &HeaderHandler::processWwwAuth}, + {HeaderType::Auth, &HeaderHandler::processAuth}, + } {} + +int Decoder::HeaderHandler::processPath(absl::string_view& header) { + metadata()->deleteInstipOperation(rawOffset(), header); + metadata()->addEPOperation(rawOffset(), header, parent_.parent_.getOwnDomain(), + parent_.parent_.getDomainMatchParamName()); + return 0; +} + +int Decoder::HeaderHandler::processRoute(absl::string_view& header) { + if (!isFirstRoute()) { + return 0; + } + setFirstRoute(false); + + if (auto loc = header.find(";ep="); loc != absl::string_view::npos) { + // No "" of ep string + auto start = loc + strlen(";ep="); + if (auto end = header.find_first_of(";>", start); end != absl::string_view::npos) { + metadata()->setRouteEP(header.substr(start, end - start)); + } + } + + metadata()->setTopRoute(header); + metadata()->setDomain(header, parent_.parent_.getDomainMatchParamName()); + return 0; +} + +int Decoder::HeaderHandler::processRecordRoute(absl::string_view& header) { + if (!isFirstRecordRoute()) { + return 0; + } + + setFirstRecordRoute(false); + + metadata()->addEPOperation(rawOffset(), header, parent_.parent_.getOwnDomain(), + parent_.parent_.getDomainMatchParamName()); + return 0; +} + +int Decoder::HeaderHandler::processWwwAuth(absl::string_view& header) { + metadata()->addOpaqueOperation(rawOffset(), header); + return 0; +} + +int Decoder::HeaderHandler::processAuth(absl::string_view& header) { + auto loc = header.find(", opaque="); + if (loc == absl::string_view::npos) { + return 0; + } + // has "" + auto start = loc + strlen(", opaque=\""); + auto end = header.find("\"", start); + if (end == absl::string_view::npos) { + return 0; + } + + metadata()->setRouteOpaque(header.substr(start, end - start - 1)); + return 0; +} + +// +// 200 OK Header Handler +// +int Decoder::OK200HeaderHandler::processCseq(absl::string_view& header) { + if (header.find("INVITE") != absl::string_view::npos) { + metadata()->setRespMethodType(MethodType::Invite); + } else { + /* need to set a value, else when processRecordRoute, + *(metadata()->respMethodType() != MethodType::Invite) always false + * TODO: need to handle non-invite 200OK + */ + metadata()->setRespMethodType(MethodType::NullMethod); + } + return 0; +} + +int Decoder::HeaderHandler::processContact(absl::string_view& header) { + metadata()->deleteInstipOperation(rawOffset(), header); + metadata()->addEPOperation(rawOffset(), header, parent_.parent_.getOwnDomain(), + parent_.parent_.getDomainMatchParamName()); + + return 0; +} + +int Decoder::HeaderHandler::processServiceRoute(absl::string_view& header) { + if (!isFirstServiceRoute()) { + return 0; + } + setFirstServiceRoute(false); + + metadata()->addEPOperation(rawOffset(), header, parent_.parent_.getOwnDomain(), + parent_.parent_.getDomainMatchParamName()); + return 0; +} + +// +// SUBSCRIBE Header Handler +// +int Decoder::SUBSCRIBEHeaderHandler::processEvent(absl::string_view& header) { + auto& parent = dynamic_cast(this->parent_); + parent.setEventType(StringUtil::trim(header.substr(header.find("Event:") + strlen("Event:")))); + return 0; +} + +void Decoder::REGISTERHandler::parseHeader(HeaderType& type, absl::string_view& header) { + switch (type) { + case HeaderType::Route: + handler_->processRoute(header); + break; + case HeaderType::Via: + handler_->processVia(header); + break; + case HeaderType::Contact: + handler_->processContact(header); + break; + case HeaderType::Path: + handler_->processPath(header); + break; + case HeaderType::RRoute: + handler_->processRecordRoute(header); + break; + case HeaderType::Auth: + handler_->processAuth(header); + break; + default: + break; + } +} + +void Decoder::INVITEHandler::parseHeader(HeaderType& type, absl::string_view& header) { + switch (type) { + case HeaderType::Via: + handler_->processVia(header); + break; + case HeaderType::Route: + handler_->processRoute(header); + break; + case HeaderType::RRoute: + handler_->processRecordRoute(header); + break; + case HeaderType::Contact: + handler_->processContact(header); + break; + default: + break; + } +} + +void Decoder::OK200Handler::parseHeader(HeaderType& type, absl::string_view& header) { + switch (type) { + case HeaderType::Cseq: + handler_->processCseq(header); + break; + case HeaderType::Contact: + handler_->processContact(header); + break; + case HeaderType::RRoute: + handler_->processRecordRoute(header); + break; + case HeaderType::Via: + handler_->processVia(header); + break; + case HeaderType::Path: + handler_->processPath(header); + break; + case HeaderType::SRoute: + handler_->processServiceRoute(header); + break; + default: + break; + } +} + +void Decoder::GeneralHandler::parseHeader(HeaderType& type, absl::string_view& header) { + switch (type) { + case HeaderType::Route: + handler_->processRoute(header); + break; + case HeaderType::Via: + handler_->processVia(header); + break; + case HeaderType::Contact: + handler_->processContact(header); + break; + case HeaderType::Path: + handler_->processPath(header); + break; + case HeaderType::RRoute: + handler_->processRecordRoute(header); + break; + default: + break; + } +} + +void Decoder::SUBSCRIBEHandler::parseHeader(HeaderType& type, absl::string_view& header) { + switch (type) { + case HeaderType::Event: + handler_->processEvent(header); + break; + case HeaderType::Route: + handler_->processRoute(header); + break; + case HeaderType::Via: + handler_->processVia(header); + break; + case HeaderType::Contact: + handler_->processContact(header); + break; + case HeaderType::RRoute: + handler_->processRecordRoute(header); + break; + default: + break; + } +} + +void Decoder::FAILURE4XXHandler::parseHeader(HeaderType& type, absl::string_view& header) { + switch (type) { + case HeaderType::Contact: + handler_->processContact(header); + break; + case HeaderType::WAuth: + handler_->processWwwAuth(header); + break; + case HeaderType::Via: + handler_->processVia(header); + break; + default: + break; + } +} + +void Decoder::OthersHandler::parseHeader(HeaderType& type, absl::string_view& header) { + switch (type) { + case HeaderType::Via: + handler_->processVia(header); + break; + case HeaderType::Contact: + handler_->processContact(header); + break; + case HeaderType::Path: + handler_->processPath(header); + break; + case HeaderType::RRoute: + handler_->processRecordRoute(header); + break; + case HeaderType::SRoute: + handler_->processServiceRoute(header); + break; + default: + break; + } +} + +std::shared_ptr Decoder::MessageFactory::create(MethodType type, + Decoder& parent) { + switch (type) { + case MethodType::Invite: + return std::make_shared(parent); + case MethodType::Ok200: + return std::make_shared(parent); + case MethodType::Register: + return std::make_shared(parent); + case MethodType::Subscribe: + return std::make_shared(parent); + case MethodType::Failure4xx: + return std::make_shared(parent); + case MethodType::Ack: + case MethodType::Bye: + case MethodType::Cancel: + return std::make_shared(parent); + default: + return std::make_shared(parent); + } +} + +int Decoder::decode() { + auto& metadata = metadata_; + absl::string_view msg = absl::string_view(metadata->rawMsg()); + + std::shared_ptr handler; + + while (!msg.empty()) { + std::string::size_type crlf = msg.find("\r\n"); + // After message reassemble, this condition could not be true + // if (crlf == absl::string_view::npos) { + // break; + // } + + if (current_header_ == HeaderType::TopLine) { + // Sip Request Line + absl::string_view sip_line = msg.substr(0, crlf); + + parseTopLine(sip_line); + current_header_ = HeaderType::Other; + + handler = MessageFactory::create(metadata->methodType(), *this); + } else { + // Normal Header Line + absl::string_view sip_line = msg.substr(0, crlf); + auto [current_header, header_value] = sipHeaderType(sip_line); + this->current_header_ = current_header; + handler->parseHeader(current_header, sip_line); + } + + msg = msg.substr(crlf + strlen("\r\n")); + raw_offset_ += crlf + strlen("\r\n"); + +#if __cplusplus > 201703L + if (msg.starts_with("\r\n")) { +#else + if (msg[0] == '\r' && msg[1] == '\n') { +#endif + break; + } + } + + if (!metadata->topRoute().has_value() && metadata->msgType() == MsgType::Request) { + metadata->setDomain(metadata->requestURI().value(), getDomainMatchParamName()); + } + return 0; +} + +int Decoder::HeaderHandler::processVia(absl::string_view& header) { + if (!isFirstVia()) { + return 0; + } + + metadata()->setTransactionId(header); + + setFirstVia(false); + return 0; +} + +int Decoder::parseTopLine(absl::string_view& top_line) { + auto metadata = metadata_; + metadata->setMsgType(sipMsgType(top_line)); + metadata->setMethodType(sipMethod(top_line)); + + if (metadata->msgType() == MsgType::Request) { + metadata->setRequestURI(top_line); + } + + if (auto loc = top_line.find(";ep="); loc != absl::string_view::npos) { + // Need to exclude the "" of ep string + auto start = loc + strlen(";ep="); + + if (auto end = top_line.find_first_of("; ", start); end != absl::string_view::npos) { + metadata->setRouteEP(top_line.substr(start, end - start)); + } + } + return 0; +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/decoder.h b/contrib/sip_proxy/filters/network/source/decoder.h new file mode 100644 index 0000000000000..035f32dd261c6 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/decoder.h @@ -0,0 +1,392 @@ +#pragma once + +#include "envoy/buffer/buffer.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/assert.h" +#include "source/common/common/logger.h" + +#include "contrib/sip_proxy/filters/network/source/filters/filter.h" +#include "contrib/sip_proxy/filters/network/source/protocol.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +#define ALL_PROTOCOL_STATES(FUNCTION) \ + FUNCTION(StopIteration) \ + FUNCTION(WaitForData) \ + FUNCTION(TransportBegin) \ + FUNCTION(MessageBegin) \ + FUNCTION(MessageEnd) \ + FUNCTION(TransportEnd) \ + FUNCTION(Done) + +/** + * ProtocolState represents a set of states used in a state machine to decode + * Sip requests and responses. + */ +enum class State { ALL_PROTOCOL_STATES(GENERATE_ENUM) }; + +class StateNameValues { +public: + static const std::string& name(State state) { + size_t i = static_cast(state); + ASSERT(i < names().size()); + return names()[i]; + } + +private: + static const std::vector& names() { + CONSTRUCT_ON_FIRST_USE(std::vector, {ALL_PROTOCOL_STATES(GENERATE_STRING)}); + } +}; + +/** + * DecoderStateMachine is the Sip message state machine + */ +class DecoderStateMachine : public Logger::Loggable { +public: + DecoderStateMachine(MessageMetadataSharedPtr& metadata, DecoderEventHandler& handler) + : metadata_(metadata), handler_(handler), state_(State::TransportBegin) {} + + /** + * Consumes as much data from the configured Buffer as possible and executes + * the decoding state machine. Returns ProtocolState::WaitForData if more data + * is required to complete processing of a message. Returns + * ProtocolState::Done when the end of a message is successfully processed. + * Once the Done state is reached, further invocations of run return + * immediately with Done. + * + * @param buffer a buffer containing the remaining data to be processed + * @return ProtocolState returns with ProtocolState::WaitForData or + * ProtocolState::Done + * @throw Envoy Exception if thrown by the underlying Protocol + */ + State run(); + + /** + * @return the current ProtocolState + */ + State currentState() const { return state_; } + + /** + * Set the current state. Used for testing only. + */ + void setCurrentState(State state) { state_ = state; } + +private: + friend class SipDecoderTest; + struct DecoderStatus { + DecoderStatus(State next_state) : next_state_(next_state){}; + DecoderStatus(State next_state, FilterStatus filter_status) + : next_state_(next_state), filter_status_(filter_status){}; + + State next_state_; + absl::optional filter_status_; + }; + + // These functions map directly to the matching ProtocolState values. Each + // returns the next state or ProtocolState::WaitForData if more data is + // required. + DecoderStatus transportBegin(); + DecoderStatus messageBegin(); + DecoderStatus messageEnd(); + DecoderStatus transportEnd(); + + // handleState delegates to the appropriate method based on state_. + DecoderStatus handleState(); + + MessageMetadataSharedPtr metadata_; + DecoderEventHandler& handler_; + State state_; +}; + +using DecoderStateMachinePtr = std::unique_ptr; + +class DecoderCallbacks { +public: + virtual ~DecoderCallbacks() = default; + + /** + * @return DecoderEventHandler& a new DecoderEventHandler for a message. + */ + virtual DecoderEventHandler& newDecoderEventHandler(MessageMetadataSharedPtr metadata) PURE; + virtual absl::string_view getLocalIp() PURE; + virtual std::string getOwnDomain() PURE; + virtual std::string getDomainMatchParamName() PURE; +}; + +/** + * Decoder encapsulates a configured Transport and Protocol and provides the + * ability to decode Sip messages. + */ +class Decoder : public Logger::Loggable { +public: + Decoder(DecoderCallbacks& callbacks); + + /** + * Drains data from the given buffer while executing a state machine over the + * data. + * + * @param data a Buffer containing Sip protocol data + * @return FilterStatus::StopIteration when waiting for filter continuation, + * Continue otherwise. + * @throw EnvoyException on Sip protocol errors + */ + FilterStatus onData(Buffer::Instance& data); + std::string getOwnDomain() { return callbacks_.getOwnDomain(); } + std::string getDomainMatchParamName() { return callbacks_.getDomainMatchParamName(); } + +protected: + MessageMetadataSharedPtr metadata() { return metadata_; } + +private: + friend class SipConnectionManagerTest; + friend class SipDecoderTest; + struct ActiveRequest { + ActiveRequest(DecoderEventHandler& handler) : handler_(handler) {} + + DecoderEventHandler& handler_; + }; + using ActiveRequestPtr = std::unique_ptr; + + void complete(); + + int reassemble(Buffer::Instance& data); + + /** + * After the data reassembled, parse the data and handle them + * @param data string + * @param length actual length of data, data.length() may less + * than length when other data after data. + */ + FilterStatus onDataReady(Buffer::Instance& data); + + int decode(); + + HeaderType currentHeader() { return current_header_; } + size_t rawOffset() { return raw_offset_; } + void setCurrentHeader(HeaderType data) { current_header_ = data; } + + bool isFirstVia() { return first_via_; } + void setFirstVia(bool flag) { first_via_ = flag; } + bool isFirstRoute() { return first_route_; } + void setFirstRoute(bool flag) { first_route_ = flag; } + bool isFirstRecordRoute() { return first_record_route_; } + void setFirstRecordRoute(bool flag) { first_record_route_ = flag; } + bool isFirstServiceRoute() { return first_service_route_; } + void setFirstServiceRoute(bool flag) { first_service_route_ = flag; } + + auto sipHeaderType(absl::string_view sip_line); + MsgType sipMsgType(absl::string_view top_line); + MethodType sipMethod(absl::string_view top_line); + + int parseTopLine(absl::string_view& top_line); + + HeaderType current_header_{HeaderType::TopLine}; + size_t raw_offset_{0}; + + bool first_via_{true}; + bool first_route_{true}; + bool first_record_route_{true}; + bool first_service_route_{true}; + + class MessageHandler; + class HeaderHandler { + public: + HeaderHandler(MessageHandler& parent); + virtual ~HeaderHandler() = default; + + using HeaderProcessor = + absl::flat_hash_map>; + + virtual int processVia(absl::string_view& header); + virtual int processContact(absl::string_view& header); + virtual int processPath(absl::string_view& header); + virtual int processEvent(absl::string_view& header) { + UNREFERENCED_PARAMETER(header); + return 0; + }; + virtual int processRoute(absl::string_view& header); + virtual int processCseq(absl::string_view& header) { + UNREFERENCED_PARAMETER(header); + return 0; + } + virtual int processRecordRoute(absl::string_view& header); + virtual int processServiceRoute(absl::string_view& header); + virtual int processWwwAuth(absl::string_view& header); + virtual int processAuth(absl::string_view& header); + + MessageMetadataSharedPtr metadata() { return parent_.metadata(); } + + HeaderType currentHeader() { return parent_.currentHeader(); } + size_t rawOffset() { return parent_.rawOffset(); } + bool isFirstVia() { return parent_.isFirstVia(); } + bool isFirstRoute() { return parent_.isFirstRoute(); } + bool isFirstRecordRoute() { return parent_.isFirstRecordRoute(); } + bool isFirstServiceRoute() { return parent_.isFirstServiceRoute(); } + void setFirstVia(bool flag) { parent_.setFirstVia(flag); } + void setFirstRoute(bool flag) { parent_.setFirstRoute(flag); } + void setFirstRecordRoute(bool flag) { parent_.setFirstRecordRoute(flag); } + void setFirstServiceRoute(bool flag) { parent_.setFirstServiceRoute(flag); } + + MessageHandler& parent_; + HeaderProcessor header_processors_; + }; + + class MessageHandler { + public: + MessageHandler(std::shared_ptr handler, Decoder& parent) + : parent_(parent), handler_(std::move(handler)) {} + virtual ~MessageHandler() = default; + + virtual void parseHeader(HeaderType& type, absl::string_view& header) PURE; + + MessageMetadataSharedPtr metadata() { return parent_.metadata(); } + HeaderType currentHeader() { return parent_.currentHeader(); } + size_t rawOffset() { return parent_.rawOffset(); } + bool isFirstVia() { return parent_.isFirstVia(); } + bool isFirstRoute() { return parent_.isFirstRoute(); } + bool isFirstRecordRoute() { return parent_.isFirstRecordRoute(); } + bool isFirstServiceRoute() { return parent_.isFirstServiceRoute(); } + void setFirstVia(bool flag) { parent_.setFirstVia(flag); } + void setFirstRoute(bool flag) { parent_.setFirstRoute(flag); } + void setFirstRecordRoute(bool flag) { parent_.setFirstRecordRoute(flag); } + void setFirstServiceRoute(bool flag) { parent_.setFirstServiceRoute(flag); } + + Decoder& parent_; + + protected: + std::shared_ptr handler_; + // Decoder& parent_; + }; + + class REGISTERHeaderHandler : public HeaderHandler { + public: + using HeaderHandler::HeaderHandler; + }; + + class INVITEHeaderHandler : public HeaderHandler { + public: + using HeaderHandler::HeaderHandler; + }; + + class OK200HeaderHandler : public HeaderHandler { + public: + using HeaderHandler::HeaderHandler; + int processCseq(absl::string_view& header) override; + }; + + class GeneralHeaderHandler : public HeaderHandler { + public: + using HeaderHandler::HeaderHandler; + }; + + class SUBSCRIBEHeaderHandler : public HeaderHandler { + public: + using HeaderHandler::HeaderHandler; + int processEvent(absl::string_view& header) override; + }; + + class FAILURE4XXHeaderHandler : public HeaderHandler { + public: + using HeaderHandler::HeaderHandler; + }; + + class REGISTERHandler : public MessageHandler { + public: + REGISTERHandler(Decoder& parent) + : MessageHandler(std::make_shared(*this), parent) {} + ~REGISTERHandler() override = default; + + void parseHeader(HeaderType& type, absl::string_view& header) override; + }; + + class INVITEHandler : public MessageHandler { + public: + INVITEHandler(Decoder& parent) + : MessageHandler(std::make_shared(*this), parent) {} + ~INVITEHandler() override = default; + + void parseHeader(HeaderType& type, absl::string_view& header) override; + }; + + class OK200Handler : public MessageHandler { + public: + OK200Handler(Decoder& parent) + : MessageHandler(std::make_shared(*this), parent) {} + ~OK200Handler() override = default; + + void parseHeader(HeaderType& type, absl::string_view& header) override; + }; + + // This is used to handle ACK/BYE/CANCEL + class GeneralHandler : public MessageHandler { + public: + GeneralHandler(Decoder& parent) + : MessageHandler(std::make_shared(*this), parent) {} + ~GeneralHandler() override = default; + + void parseHeader(HeaderType& type, absl::string_view& header) override; + }; + + class SUBSCRIBEHandler : public MessageHandler { + public: + SUBSCRIBEHandler(Decoder& parent) + : MessageHandler(std::make_shared(*this), parent) {} + ~SUBSCRIBEHandler() override = default; + void parseHeader(HeaderType& type, absl::string_view& header) override; + void setEventType(absl::string_view value) { + if (value == "reg") { + event_type_ = EventType::REG; + } else { + event_type_ = EventType::OTHERS; + } + } + + private: + enum class EventType { REG, OTHERS }; + + EventType event_type_; + }; + + // This is used to handle Other Message + class OthersHandler : public MessageHandler { + public: + OthersHandler(Decoder& parent) + : MessageHandler(std::make_shared(*this), parent) {} + ~OthersHandler() override = default; + + void parseHeader(HeaderType& type, absl::string_view& header) override; + }; + + class FAILURE4XXHandler : public MessageHandler { + public: + FAILURE4XXHandler(Decoder& parent) + : MessageHandler(std::make_shared(*this), parent) {} + ~FAILURE4XXHandler() override = default; + + void parseHeader(HeaderType& type, absl::string_view& header) override; + }; + + class MessageFactory { + public: + static std::shared_ptr create(MethodType type, Decoder& parent); + }; + + DecoderCallbacks& callbacks_; + ActiveRequestPtr request_; + MessageMetadataSharedPtr metadata_; + DecoderStateMachinePtr state_machine_; + bool start_new_message_{true}; +}; + +using DecoderPtr = std::unique_ptr; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/decoder_events.h b/contrib/sip_proxy/filters/network/source/decoder_events.h new file mode 100644 index 0000000000000..625b97322e7e4 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/decoder_events.h @@ -0,0 +1,55 @@ +#pragma once + +#include "contrib/sip_proxy/filters/network/source/metadata.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +enum class FilterStatus { + // Continue filter chain iteration. + Continue, + + // Stop iterating over filters in the filter chain. + StopIteration +}; + +class DecoderEventHandler { +public: + virtual ~DecoderEventHandler() = default; + + /** + * Indicates the start of a Sip transport frame was detected. Unframed transports generate + * simulated start messages. + * @param metadata MessageMetadataSharedPtr describing as much as is currently known about the + * message + */ + virtual FilterStatus transportBegin(MessageMetadataSharedPtr metadata) PURE; + + /** + * Indicates the end of a Sip transport frame was detected. Unframed transport generate + * simulated complete messages. + */ + virtual FilterStatus transportEnd() PURE; + + /** + * Indicates that the start of a Sip protocol message was detected. + * @param metadata MessageMetadataSharedPtr describing the message + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus messageBegin(MessageMetadataSharedPtr metadata) PURE; + + /** + * Indicates that the end of a Sip protocol message was detected. + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus messageEnd() PURE; +}; + +using DecoderEventHandlerSharedPtr = std::shared_ptr; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/encoder.cc b/contrib/sip_proxy/filters/network/source/encoder.cc new file mode 100644 index 0000000000000..1bc451d6e4b51 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/encoder.cc @@ -0,0 +1,65 @@ +#include "contrib/sip_proxy/filters/network/source/encoder.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +void EncoderImpl::encode(const MessageMetadataSharedPtr& metadata, Buffer::Instance& out) { + std::string output = ""; + std::string& raw_msg = metadata->rawMsg(); + std::sort(metadata->operationList().begin(), metadata->operationList().end()); + + size_t previous_position = 0; + for (auto& operation : metadata->operationList()) { + switch (operation.type_) { + case OperationType::Insert: { + std::string value = absl::get(operation.value_).value_; + if (value == ";ep=" || value == ",opaque=") { + if (metadata->ep().has_value() && metadata->ep().value().length() > 0) { + output += raw_msg.substr(previous_position, operation.position_ - previous_position); + previous_position = operation.position_; + + output += absl::get(operation.value_).value_; + if (value == ",opaque=") { + output += "\""; + } + output += std::string(metadata->ep().value()); + if (value == ",opaque=") { + output += "\""; + } + } + } else { + output += raw_msg.substr(previous_position, operation.position_ - previous_position); + previous_position = operation.position_; + + output += absl::get(operation.value_).value_; + } + break; + } + case OperationType::Modify: + output += raw_msg.substr(previous_position, operation.position_ - previous_position); + previous_position = operation.position_; + + output += absl::get(operation.value_).dest_; + previous_position += absl::get(operation.value_).src_length_; + break; + case OperationType::Delete: + output += raw_msg.substr(previous_position, operation.position_ - previous_position); + previous_position = operation.position_; + + previous_position += absl::get(operation.value_).length_; + break; + default: + break; + } + } + + output += raw_msg.substr(previous_position); + out.add(output); +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/encoder.h b/contrib/sip_proxy/filters/network/source/encoder.h new file mode 100644 index 0000000000000..1cab707a54336 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/encoder.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "envoy/buffer/buffer.h" +#include "envoy/common/exception.h" + +#include "source/common/common/assert.h" +#include "source/common/common/logger.h" + +#include "contrib/sip_proxy/filters/network/source/metadata.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +class Encoder : public Logger::Loggable { +public: + virtual ~Encoder() = default; + virtual void encode(const MessageMetadataSharedPtr& metadata, Buffer::Instance& out) PURE; +}; + +class EncoderImpl : public Encoder { +public: + void encode(const MessageMetadataSharedPtr& metadata, Buffer::Instance& out) override; +}; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/filters/BUILD b/contrib/sip_proxy/filters/network/source/filters/BUILD new file mode 100644 index 0000000000000..a8e9b8d9b1e09 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/filters/BUILD @@ -0,0 +1,60 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_library( + name = "filter_config_interface", + hdrs = ["filter_config.h"], + deps = [ + ":filter_interface", + "//envoy/config:typed_config_interface", + "//envoy/server:filter_config_interface", + "//source/common/common:macros", + "//source/common/protobuf:cc_wkt_protos", + ], +) + +envoy_cc_library( + name = "factory_base_lib", + hdrs = ["factory_base.h"], + deps = [ + ":filter_config_interface", + "//source/common/protobuf:utility_lib", + ], +) + +envoy_cc_library( + name = "filter_interface", + hdrs = ["filter.h"], + deps = [ + "//contrib/sip_proxy/filters/network/source:decoder_events_lib", + "//contrib/sip_proxy/filters/network/source:protocol_interface", + "//contrib/sip_proxy/filters/network/source:sip_lib", + "//contrib/sip_proxy/filters/network/source/router:router_interface", + "//envoy/buffer:buffer_interface", + "//envoy/network:connection_interface", + "//envoy/stream_info:stream_info_interface", + ], +) + +envoy_cc_library( + name = "well_known_names", + hdrs = ["well_known_names.h"], + deps = [ + "//source/common/singleton:const_singleton", + ], +) + +envoy_cc_library( + name = "pass_through_filter_lib", + hdrs = ["pass_through_filter.h"], + deps = [ + ":filter_interface", + ], +) diff --git a/contrib/sip_proxy/filters/network/source/filters/factory_base.h b/contrib/sip_proxy/filters/network/source/filters/factory_base.h new file mode 100644 index 0000000000000..05fd66503e70e --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/filters/factory_base.h @@ -0,0 +1,46 @@ +#pragma once + +#include "source/common/protobuf/utility.h" + +#include "contrib/sip_proxy/filters/network/source/filters/filter_config.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace SipFilters { + +template class FactoryBase : public NamedSipFilterConfigFactory { +public: + FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& proto_config, + const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) override { + return createFilterFactoryFromProtoTyped(MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + stats_prefix, context); + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return name_; } + +protected: + FactoryBase(const std::string& name) : name_(name) {} + +private: + virtual FilterFactoryCb + createFilterFactoryFromProtoTyped(const ConfigProto& proto_config, + const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) PURE; + + const std::string name_; +}; + +} // namespace SipFilters +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/filters/filter.h b/contrib/sip_proxy/filters/network/source/filters/filter.h new file mode 100644 index 0000000000000..fd19df1f282f3 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/filters/filter.h @@ -0,0 +1,166 @@ +#pragma once + +#include +#include +#include + +#include "envoy/buffer/buffer.h" +#include "envoy/network/connection.h" +#include "envoy/stream_info/stream_info.h" + +#include "contrib/sip_proxy/filters/network/source/decoder_events.h" +#include "contrib/sip_proxy/filters/network/source/protocol.h" +#include "contrib/sip_proxy/filters/network/source/router/router.h" +#include "contrib/sip_proxy/filters/network/source/sip.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace SipFilters { + +enum class ResponseStatus { + MoreData = 0, // The upstream response requires more data. + Complete = 1, // The upstream response is complete. + Reset = 2, // The upstream response is invalid and its connection must be reset. +}; + +/** + * Decoder filter callbacks add additional callbacks. + */ +class DecoderFilterCallbacks { +public: + virtual ~DecoderFilterCallbacks() = default; + + /** + * @return uint64_t the ID of the originating stream for logging purposes. + */ + virtual uint64_t streamId() const PURE; + + /** + * @return string the ID of the transaction. + */ + virtual std::string transactionId() const PURE; + + /** + * @return const Network::Connection* the originating connection, or nullptr if there is none. + */ + virtual const Network::Connection* connection() const PURE; + + /** + * @return RouteConstSharedPtr the route for the current request. + */ + virtual Router::RouteConstSharedPtr route() PURE; + + /** + * Create a locally generated response using the provided response object. + * @param response DirectResponse the response to send to the downstream client + * @param end_stream if true, the downstream connection should be closed after this response + */ + virtual void sendLocalReply(const SipProxy::DirectResponse& response, bool end_stream) PURE; + + /** + * Indicates the start of an upstream response. May only be called once. + * @param transport the transport used by the upstream response + * @param protocol the protocol used by the upstream response + */ + virtual void startUpstreamResponse() PURE; + + /** + * Called with upstream response data. + * @param data supplies the upstream's data + * @return ResponseStatus indicating if the upstream response requires more data, is complete, + * or if an error occurred requiring the upstream connection to be reset. + */ + virtual ResponseStatus upstreamData(MessageMetadataSharedPtr metadata) PURE; + + /** + * Reset the downstream connection. + */ + virtual void resetDownstreamConnection() PURE; + + /** + * @return StreamInfo for logging purposes. + */ + virtual StreamInfo::StreamInfo& streamInfo() PURE; + + virtual std::shared_ptr transactionInfos() PURE; + virtual std::shared_ptr settings() PURE; + virtual void onReset() PURE; +}; + +/** + * Decoder filter interface. + */ +class DecoderFilter : public virtual DecoderEventHandler { +public: + ~DecoderFilter() override = default; + + /** + * This routine is called prior to a filter being destroyed. This may happen after normal stream + * finish (both downstream and upstream) or due to reset. Every filter is responsible for making + * sure that any async events are cleaned up in the context of this routine. This includes timers, + * network calls, etc. The reason there is an onDestroy() method vs. doing this type of cleanup + * in the destructor is due to the deferred deletion model that Envoy uses to avoid stack unwind + * complications. Filters must not invoke either encoder or decoder filter callbacks after having + * onDestroy() invoked. + */ + virtual void onDestroy() PURE; + + /** + * Called by the connection manager once to initialize the filter decoder callbacks that the + * filter should use. Callbacks will not be invoked by the filter after onDestroy() is called. + */ + virtual void setDecoderFilterCallbacks(DecoderFilterCallbacks& callbacks) PURE; +}; + +using DecoderFilterSharedPtr = std::shared_ptr; + +/** + * These callbacks are provided by the connection manager to the factory so that the factory can + * build the filter chain in an application specific way. + */ +class FilterChainFactoryCallbacks { +public: + virtual ~FilterChainFactoryCallbacks() = default; + + /** + * Add a decoder filter that is used when reading connection data. + * @param filter supplies the filter to add. + */ + virtual void addDecoderFilter(DecoderFilterSharedPtr filter) PURE; +}; + +/** + * This function is used to wrap the creation of a Sip filter chain for new connections as they + * come in. Filter factories create the function at configuration initialization time, and then + * they are used at runtime. + * @param callbacks supplies the callbacks for the stream to install filters to. Typically the + * function will install a single filter, but it's technically possibly to install more than one + * if desired. + */ +using FilterFactoryCb = std::function; + +/** + * A FilterChainFactory is used by a connection manager to create a Sip level filter chain when + * a new connection is created. Typically it would be implemented by a configuration engine that + * would install a set of filters that are able to process an application scenario on top of a + * stream of Sip requests. + */ +class FilterChainFactory { +public: + virtual ~FilterChainFactory() = default; + + /** + * Called when a new Sip stream is created on the connection. + * @param callbacks supplies the "sink" that is used for actually creating the filter chain. @see + * FilterChainFactoryCallbacks. + */ + virtual void createFilterChain(FilterChainFactoryCallbacks& callbacks) PURE; +}; + +} // namespace SipFilters +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/filters/filter_config.h b/contrib/sip_proxy/filters/network/source/filters/filter_config.h new file mode 100644 index 0000000000000..1748556f5a0b3 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/filters/filter_config.h @@ -0,0 +1,45 @@ +#pragma once + +#include "envoy/config/typed_config.h" +#include "envoy/server/filter_config.h" + +#include "source/common/common/macros.h" +#include "source/common/protobuf/protobuf.h" + +#include "contrib/sip_proxy/filters/network/source/filters/filter.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace SipFilters { + +/** + * Implemented by each Sip filter and registered via Registry::registerFactory or the + * convenience class RegisterFactory. + */ +class NamedSipFilterConfigFactory : public Envoy::Config::TypedFactory { +public: + ~NamedSipFilterConfigFactory() override = default; + + /** + * Create a particular sip filter factory implementation. If the implementation is unable to + * produce a factory with the provided parameters, it should throw an EnvoyException in the case + * of general error. The returned callback should always be initialized. + * @param config supplies the configuration for the filter + * @param stat_prefix prefix for stat logging + * @param context supplies the filter's context. + * @return FilterFactoryCb the factory creation function. + */ + virtual FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& config, const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) PURE; + + std::string category() const override { return "envoy.sip_proxy.filters"; } +}; + +} // namespace SipFilters +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/filters/pass_through_filter.h b/contrib/sip_proxy/filters/network/source/filters/pass_through_filter.h new file mode 100644 index 0000000000000..bc8c6cc9c5370 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/filters/pass_through_filter.h @@ -0,0 +1,46 @@ +#pragma once + +#include "absl/strings/string_view.h" +#include "contrib/sip_proxy/filters/network/source/filters/filter.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace SipFilters { + +/** + * Pass through Sip decoder filter. Continue at each decoding state within the series of + * transitions. + */ +class PassThroughDecoderFilter : public DecoderFilter { +public: + // SipDecoderFilter + void onDestroy() override {} + + void setDecoderFilterCallbacks(DecoderFilterCallbacks& callbacks) override { + decoder_callbacks_ = &callbacks; + }; + + // Sip Decoder State Machine + SipProxy::FilterStatus transportBegin(SipProxy::MessageMetadataSharedPtr) override { + return SipProxy::FilterStatus::Continue; + } + + SipProxy::FilterStatus transportEnd() override { return SipProxy::FilterStatus::Continue; } + + SipProxy::FilterStatus messageBegin(SipProxy::MessageMetadataSharedPtr) override { + return SipProxy::FilterStatus::Continue; + } + + SipProxy::FilterStatus messageEnd() override { return SipProxy::FilterStatus::Continue; } + +protected: + DecoderFilterCallbacks* decoder_callbacks_{}; +}; + +} // namespace SipFilters +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/filters/well_known_names.h b/contrib/sip_proxy/filters/network/source/filters/well_known_names.h new file mode 100644 index 0000000000000..d1d712ea67a17 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/filters/well_known_names.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "source/common/singleton/const_singleton.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace SipFilters { + +/** + * Well-known http filter names. + * NOTE: New filters should use the well known name: envoy.filters.sip.name. + */ +class SipFilterNameValues { +public: + // Router filter + const std::string ROUTER = "envoy.filters.sip.router"; +}; + +using SipFilterNames = ConstSingleton; + +} // namespace SipFilters + +const std::string SipProxy = "envoy.filters.network.sip_proxy"; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/metadata.h b/contrib/sip_proxy/filters/network/source/metadata.h new file mode 100644 index 0000000000000..e422385f1d70e --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/metadata.h @@ -0,0 +1,209 @@ +#pragma once + +#include +#include +#include +#include + +#include "source/common/common/assert.h" +#include "source/common/common/logger.h" + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "contrib/sip_proxy/filters/network/source/operation.h" +#include "contrib/sip_proxy/filters/network/source/sip.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +/** + * MessageMetadata encapsulates metadata about Sip messages. The various fields are considered + * optional since they may come from either the transport or protocol in some cases. Unless + * otherwise noted, accessor methods throw absl::bad_optional_access if the corresponding value has + * not been set. + */ +class MessageMetadata : public Logger::Loggable { +public: + MessageMetadata() = default; + MessageMetadata(std::string&& raw_msg) : raw_msg_(std::move(raw_msg)) {} + + MsgType msgType() { return msg_type_; } + MethodType methodType() { return method_type_; } + MethodType respMethodType() { return resp_method_type_; } + absl::optional ep() { return ep_; } + std::vector& operationList() { return operation_list_; } + absl::optional routeEP() { return route_ep_; } + absl::optional routeOpaque() { return route_opaque_; } + + absl::optional requestURI() { return request_uri_; } + absl::optional topRoute() { return top_route_; } + absl::optional domain() { return domain_; } + absl::optional transactionId() { return transaction_id_; } + absl::optional destination() { return destination_; } + + std::string& rawMsg() { return raw_msg_; } + + void setMsgType(MsgType data) { msg_type_ = data; } + void setMethodType(MethodType data) { method_type_ = data; } + void setRespMethodType(MethodType data) { resp_method_type_ = data; } + void setOperation(Operation op) { operation_list_.emplace_back(op); } + void setEP(absl::string_view data) { ep_ = data; } + void setRouteEP(absl::string_view data) { route_ep_ = data; } + void setRouteOpaque(absl::string_view data) { route_opaque_ = data; } + + void setRequestURI(absl::string_view data) { request_uri_ = data; } + void setTopRoute(absl::string_view data) { top_route_ = data; } + void setDomain(absl::string_view header, std::string domain_match_param_name) { + domain_ = getDomain(header, domain_match_param_name); + } + + // void addEPOperation(size_t raw_offset, absl::string_view& header, std::string& own_domain, + // std::string& domain_match_param_name) { + void addEPOperation(size_t raw_offset, absl::string_view& header, std::string own_domain, + std::string domain_match_param_name) { + ENVOY_LOG(debug, "header: {}\n own_domain: {}\n domain_match_param_name: {}", header, + own_domain, domain_match_param_name); + if (header.find(";ep=") != absl::string_view::npos) { + // already Contact have ep + return; + } + auto pos = header.find(">"); + if (pos == absl::string_view::npos) { + // no url + return; + } + + // Get domain + absl::string_view domain = getDomain(header, domain_match_param_name); + + // Compare the domain + if (domain != own_domain) { + ENVOY_LOG(debug, "header domain:{} not matches own_domain:{}", domain, own_domain); + return; + } + + setOperation(Operation(OperationType::Insert, raw_offset + pos, InsertOperationValue(";ep="))); + } + + void addOpaqueOperation(size_t raw_offset, absl::string_view& header) { + if (header.find("opaque=") != absl::string_view::npos) { + // already has opaque + return; + } + auto pos = header.length(); + setOperation( + Operation(OperationType::Insert, raw_offset + pos, InsertOperationValue(",opaque="))); + } + + void deleteInstipOperation(size_t raw_offset, absl::string_view& header) { + // Delete inst-ip and remove "sip:" in x-suri + if (auto pos = header.find(";inst-ip="); pos != absl::string_view::npos) { + setOperation( + Operation(OperationType::Delete, raw_offset + pos, + DeleteOperationValue( + header.substr(pos, header.find_first_of(";>", pos + 1) - pos).size()))); + auto xsuri = header.find("sip:pcsf-cfed"); + setOperation(Operation(OperationType::Delete, raw_offset + xsuri, DeleteOperationValue(4))); + } + } + + // input is the full SIP header + void setTransactionId(absl::string_view data) { + auto start_index = data.find("branch="); + if (start_index == absl::string_view::npos) { + return; + } + start_index += strlen("branch="); + + auto end_index = data.find_first_of(";>", start_index); + if (end_index == absl::string_view::npos) { + end_index = data.size(); + } + transaction_id_ = data.substr(start_index, end_index - start_index); + } + + void setDestination(absl::string_view destination) { destination_ = destination; } + /*only used in UT*/ + void resetTransactionId() { transaction_id_.reset(); } + +private: + MsgType msg_type_; + MethodType method_type_; + MethodType resp_method_type_; + std::vector operation_list_; + absl::optional ep_{}; + absl::optional pep_{}; + absl::optional route_ep_{}; + absl::optional route_opaque_{}; + + absl::optional request_uri_{}; + absl::optional top_route_{}; + absl::optional domain_{}; + absl::optional transaction_id_{}; + absl::optional destination_{}; + + std::string raw_msg_{}; + + absl::string_view getDomain(absl::string_view header, std::string domain_match_param_name) { + ENVOY_LOG(debug, "header: {}\ndomain_match_param_name: {}", header, domain_match_param_name); + + // Get domain + absl::string_view domain = ""; + + if (domain_match_param_name != "host") { + auto start = header.find(domain_match_param_name); + if (start == absl::string_view::npos) { + domain = ""; + } else { + // domain_match_param_name + "=" + // start = start + strlen(domain_match_param_name.c_str()) + strlen("=") ; + start = start + domain_match_param_name.length() + strlen("="); + if ("sip:" == header.substr(start, strlen("sip:"))) { + start += strlen("sip:"); + } + // end + auto end = header.find_first_of(":;>", start); + if (end == absl::string_view::npos) { + domain = ""; + } else { + domain = header.substr(start, end - start); + } + } + } + + // Still get host if mapped domain is empty + if (domain_match_param_name == "host" || domain == "") { + auto start = header.find("sip:"); + if (start == absl::string_view::npos) { + return ""; + } + start += strlen("sip:"); + auto end = header.find_first_of(":;>", start); + if (end == absl::string_view::npos) { + return ""; + } + + auto addr = header.substr(start, end - start); + + // Remove name in format of sip:name@addr:pos + auto pos = addr.find("@"); + if (pos == absl::string_view::npos) { + domain = header.substr(start, end - start); + } else { + pos += strlen("@"); + domain = addr.substr(pos, addr.length() - pos); + } + } + + return domain; + } +}; + +using MessageMetadataSharedPtr = std::shared_ptr; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/operation.h b/contrib/sip_proxy/filters/network/source/operation.h new file mode 100644 index 0000000000000..6cadd12a19afb --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/operation.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "absl/types/variant.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +enum class OperationType { + Invalid = 0, + Insert = 1, + Delete = 2, + Modify = 3, + Query = 4, // not used yet. +}; + +struct InsertOperationValue { + InsertOperationValue(std::string&& value) : value_(value) {} + std::string value_; +}; +struct DeleteOperationValue { + DeleteOperationValue(size_t length) : length_(length) {} + size_t length_; +}; +struct ModifyOperationValue { + ModifyOperationValue(size_t src_length, std::string&& dest) + : src_length_(src_length), dest_(dest) {} + size_t src_length_; + std::string dest_; +}; + +class Operation { +public: + Operation(OperationType type, size_t position, + absl::variant value) + : type_(type), position_(position), value_(value) {} + + // constexpr bool operator<(const Operation& other) { return this->position_ < other.position_; } + // constexpr bool operator>(const Operation& other) { return this->position_ > other.position_; } + // constexpr bool operator==(const Operation& other) { return this->position_ == other.position_; + // } constexpr bool operator!=(const Operation& other) { return this->position_ != + // other.position_; } constexpr bool operator<=(const Operation& other) { return this->position_ + // <= other.position_; } constexpr bool operator>=(const Operation& other) { return + // this->position_ >= other.position_; } constexpr bool operator<=>(Operation &other) { return + // this->position_ <=> other.position_; } + + // private: + OperationType type_; + size_t position_; + absl::variant value_; +}; + +static constexpr bool operator<(const Operation& o1, const Operation& o2) { + return o1.position_ < o2.position_; +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/protocol.h b/contrib/sip_proxy/filters/network/source/protocol.h new file mode 100644 index 0000000000000..8a24effe302bb --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/protocol.h @@ -0,0 +1,60 @@ +#pragma once + +#include "envoy/buffer/buffer.h" + +#include "contrib/sip_proxy/filters/network/source/conn_state.h" +#include "contrib/sip_proxy/filters/network/source/metadata.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +class SipSettings { +public: + SipSettings(std::chrono::milliseconds transaction_timeout, std::string own_domain, + std::string domain_match_parameter_name) + : transaction_timeout_(transaction_timeout), own_domain_(own_domain), + domain_match_parameter_name_(domain_match_parameter_name) {} + std::chrono::milliseconds transactionTimeout() { return transaction_timeout_; } + std::string ownDomain() { return own_domain_; } + std::string domainMatchParamName() { return domain_match_parameter_name_; } + +private: + std::chrono::milliseconds transaction_timeout_; + std::string own_domain_; + std::string domain_match_parameter_name_; +}; + +/** + * A DirectResponse manipulates a Protocol to directly create a Sip response message. + */ +class DirectResponse { +public: + virtual ~DirectResponse() = default; + + enum class ResponseType { + // DirectResponse encodes MessageType::Reply with success payload + SuccessReply, + + // DirectResponse encodes MessageType::Reply with an exception payload + ErrorReply, + + // DirectResponse encodes MessageType::Exception + Exception, + }; + + /** + * Encodes the response via the given Protocol. + * @param metadata the MessageMetadata for the request that generated this response + * @param proto the Protocol to be used for message encoding + * @param buffer the Buffer into which the message should be encoded + * @return ResponseType indicating whether the message is a successful or error reply or an + * exception + */ + virtual ResponseType encode(MessageMetadata& metadata, Buffer::Instance& buffer) const PURE; +}; +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/router/BUILD b/contrib/sip_proxy/filters/network/source/router/BUILD new file mode 100644 index 0000000000000..114b13328da6c --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/router/BUILD @@ -0,0 +1,58 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":router_lib", + "//contrib/sip_proxy/filters/network/source/filters:factory_base_lib", + "//contrib/sip_proxy/filters/network/source/filters:filter_config_interface", + "//contrib/sip_proxy/filters/network/source/filters:well_known_names", + "//envoy/registry", + "@envoy_api//contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "router_interface", + hdrs = ["router.h"], + external_deps = ["abseil_optional"], + deps = [ + "//contrib/sip_proxy/filters/network/source:metadata_lib", + "//envoy/router:router_interface", + ], +) + +envoy_cc_library( + name = "router_lib", + srcs = ["router_impl.cc"], + hdrs = ["router_impl.h"], + deps = [ + ":router_interface", + "//contrib/sip_proxy/filters/network/source:app_exception_lib", + "//contrib/sip_proxy/filters/network/source:conn_manager_lib", + "//contrib/sip_proxy/filters/network/source:encoder_lib", + "//contrib/sip_proxy/filters/network/source:protocol_interface", + "//contrib/sip_proxy/filters/network/source/filters:filter_interface", + "//contrib/sip_proxy/filters/network/source/filters:well_known_names", + "//envoy/tcp:conn_pool_interface", + "//envoy/upstream:cluster_manager_interface", + "//envoy/upstream:load_balancer_interface", + "//envoy/upstream:thread_local_cluster_interface", + "//source/common/common:logger_lib", + "//source/common/http:header_utility_lib", + "//source/common/router:metadatamatchcriteria_lib", + "//source/common/upstream:load_balancer_lib", + "@envoy_api//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg_cc_proto", + ], +) diff --git a/contrib/sip_proxy/filters/network/source/router/config.cc b/contrib/sip_proxy/filters/network/source/router/config.cc new file mode 100644 index 0000000000000..fb00f9735cb92 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/router/config.cc @@ -0,0 +1,35 @@ +#include "contrib/sip_proxy/filters/network/source/router/config.h" + +#include "envoy/registry/registry.h" + +#include "contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/router.pb.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/router.pb.validate.h" +#include "contrib/sip_proxy/filters/network/source/router/router_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace Router { + +SipFilters::FilterFactoryCb RouterFilterConfig::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::sip_proxy::router::v3alpha::Router& proto_config, + const std::string& stat_prefix, Server::Configuration::FactoryContext& context) { + UNREFERENCED_PARAMETER(proto_config); + + return [&context, stat_prefix](SipFilters::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addDecoderFilter( + std::make_shared(context.clusterManager(), stat_prefix, context.scope())); + }; +} + +/** + * Static registration for the router filter. @see RegisterFactory. + */ +REGISTER_FACTORY(RouterFilterConfig, SipFilters::NamedSipFilterConfigFactory); + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/router/config.h b/contrib/sip_proxy/filters/network/source/router/config.h new file mode 100644 index 0000000000000..95e1ff931017f --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/router/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include "contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/router.pb.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/router/v3alpha/router.pb.validate.h" +#include "contrib/sip_proxy/filters/network/source/filters/factory_base.h" +#include "contrib/sip_proxy/filters/network/source/filters/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace Router { + +class RouterFilterConfig + : public SipFilters::FactoryBase< + envoy::extensions::filters::network::sip_proxy::router::v3alpha::Router> { +public: + RouterFilterConfig() : FactoryBase(SipFilters::SipFilterNames::get().ROUTER) {} + +private: + SipFilters::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::sip_proxy::router::v3alpha::Router& proto_config, + const std::string& stat_prefix, Server::Configuration::FactoryContext& context) override; +}; + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/router/router.h b/contrib/sip_proxy/filters/network/source/router/router.h new file mode 100644 index 0000000000000..d8a3b80ab3256 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/router/router.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +#include "envoy/router/router.h" + +#include "contrib/sip_proxy/filters/network/source/metadata.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace Router { + +class TransactionInfo; +using TransactionInfos = std::map>; + +/** + * RouteEntry is an individual resolved route entry. + */ +class RouteEntry { +public: + virtual ~RouteEntry() = default; + + /** + * @return const std::string& the upstream cluster that owns the route. + */ + virtual const std::string& clusterName() const PURE; + + /** + * @return MetadataMatchCriteria* the metadata that a subset load balancer should match when + * selecting an upstream host + */ + virtual const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() const PURE; +}; + +/** + * Route holds the RouteEntry for a request. + */ +class Route { +public: + virtual ~Route() = default; + + /** + * @return the route entry or nullptr if there is no matching route for the request. + */ + virtual const RouteEntry* routeEntry() const PURE; +}; + +using RouteConstSharedPtr = std::shared_ptr; + +/** + * The router configuration. + */ +class Config { +public: + virtual ~Config() = default; + + /** + * Based on the incoming Sip request transport and/or protocol data, determine the target + * route for the request. + * @param metadata MessageMetadata for the message to route + * @param random_value uint64_t used to select cluster affinity + * @return the route or nullptr if there is no matching route for the request. + */ + virtual RouteConstSharedPtr route(MessageMetadata& metadata) const PURE; +}; + +using ConfigConstSharedPtr = std::shared_ptr; + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/router/router_impl.cc b/contrib/sip_proxy/filters/network/source/router/router_impl.cc new file mode 100644 index 0000000000000..e81dbabbe7860 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/router/router_impl.cc @@ -0,0 +1,487 @@ +#include "contrib/sip_proxy/filters/network/source/router/router_impl.h" + +#include + +#include "envoy/upstream/cluster_manager.h" + +#include "source/common/common/logger.h" +#include "source/common/common/utility.h" +#include "source/common/network/address_impl.h" +#include "source/common/router/metadatamatchcriteria_impl.h" + +#include "absl/strings/match.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/route.pb.h" +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" +#include "contrib/sip_proxy/filters/network/source/encoder.h" +#include "contrib/sip_proxy/filters/network/source/filters/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace Router { + +RouteEntryImplBase::RouteEntryImplBase( + const envoy::extensions::filters::network::sip_proxy::v3alpha::Route& route) + : cluster_name_(route.route().cluster()) {} + +const std::string& RouteEntryImplBase::clusterName() const { return cluster_name_; } + +const RouteEntry* RouteEntryImplBase::routeEntry() const { return this; } + +RouteConstSharedPtr RouteEntryImplBase::clusterEntry(const MessageMetadata& metadata) const { + UNREFERENCED_PARAMETER(metadata); + return shared_from_this(); +} + +GeneralRouteEntryImpl::GeneralRouteEntryImpl( + const envoy::extensions::filters::network::sip_proxy::v3alpha::Route& route) + : RouteEntryImplBase(route), domain_(route.match().domain()) {} + +RouteConstSharedPtr GeneralRouteEntryImpl::matches(MessageMetadata& metadata) const { + bool matches = metadata.domain().value() == domain_ || domain_ == "*"; + + if (matches) { + return clusterEntry(metadata); + } + + return nullptr; +} + +RouteMatcher::RouteMatcher( + const envoy::extensions::filters::network::sip_proxy::v3alpha::RouteConfiguration& config) { + using envoy::extensions::filters::network::sip_proxy::v3alpha::RouteMatch; + + for (const auto& route : config.routes()) { + switch (route.match().match_specifier_case()) { + case RouteMatch::MatchSpecifierCase::kDomain: + routes_.emplace_back(new GeneralRouteEntryImpl(route)); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + } +} + +RouteConstSharedPtr RouteMatcher::route(MessageMetadata& metadata) const { + for (const auto& route : routes_) { + RouteConstSharedPtr route_entry = route->matches(metadata); + if (nullptr != route_entry) { + return route_entry; + } + } + + return nullptr; +} + +void Router::onDestroy() { + if (!callbacks_->transactionId().empty()) { + for (auto& kv : *transaction_infos_) { + auto transaction_info = kv.second; + try { + transaction_info->getTransaction(callbacks_->transactionId()); + transaction_info->deleteTransaction(callbacks_->transactionId()); + } catch (std::out_of_range const&) { + } + } + } +} + +void Router::setDecoderFilterCallbacks(SipFilters::DecoderFilterCallbacks& callbacks) { + callbacks_ = &callbacks; + transaction_infos_ = callbacks_->transactionInfos(); + settings_ = callbacks_->settings(); +} + +FilterStatus Router::transportBegin(MessageMetadataSharedPtr metadata) { + UNREFERENCED_PARAMETER(metadata); + return FilterStatus::Continue; +} + +FilterStatus Router::transportEnd() { return FilterStatus::Continue; } + +FilterStatus Router::messageBegin(MessageMetadataSharedPtr metadata) { + if (upstream_request_ != nullptr) { + return FilterStatus::Continue; + } + + metadata_ = metadata; + route_ = callbacks_->route(); + if (!route_) { + ENVOY_STREAM_LOG(debug, "no route match domain {}", *callbacks_, metadata->domain().value()); + stats_.route_missing_.inc(); + callbacks_->sendLocalReply(AppException(AppExceptionType::UnknownMethod, "no route for method"), + true); + return FilterStatus::StopIteration; + } + + route_entry_ = route_->routeEntry(); + const std::string& cluster_name = route_entry_->clusterName(); + + Upstream::ThreadLocalCluster* cluster = cluster_manager_.getThreadLocalCluster(cluster_name); + if (!cluster) { + ENVOY_STREAM_LOG(debug, "unknown cluster '{}'", *callbacks_, cluster_name); + stats_.unknown_cluster_.inc(); + callbacks_->sendLocalReply(AppException(AppExceptionType::InternalError, + fmt::format("unknown cluster '{}'", cluster_name)), + true); + return FilterStatus::StopIteration; + } + + cluster_ = cluster->info(); + ENVOY_STREAM_LOG(debug, "cluster '{}' match domain {}", *callbacks_, cluster_name, + std::string(metadata->domain().value())); + + if (cluster_->maintenanceMode()) { + stats_.upstream_rq_maintenance_mode_.inc(); + callbacks_->sendLocalReply( + AppException(AppExceptionType::InternalError, + fmt::format("maintenance mode for cluster '{}'", cluster_name)), + true); + return FilterStatus::StopIteration; + } + + const std::shared_ptr options = + cluster_->extensionProtocolOptionsTyped(SipProxy); + + auto handle_affinity = [&](const std::shared_ptr options) { + if (options == nullptr || metadata->msgType() == MsgType::Response) { + return; + } + + if (metadata->methodType() != MethodType::Register && options->sessionAffinity()) { + if (metadata->routeEP().has_value()) { + auto host = metadata->routeEP().value(); + metadata->setDestination(host); + } + } + if (metadata->methodType() == MethodType::Register && options->registrationAffinity()) { + if (metadata->routeOpaque().has_value()) { + auto host = metadata->routeOpaque().value(); + metadata->setDestination(host); + } + } + }; + handle_affinity(options); + + auto& transaction_info = (*transaction_infos_)[cluster_name]; + + auto message_handler_with_loadbalancer = [&]() { + auto pool_data = cluster->tcpConnPool(Upstream::ResourcePriority::Default, this); + if (!pool_data) { + stats_.no_healthy_upstream_.inc(); + callbacks_->sendLocalReply( + AppException(AppExceptionType::InternalError, + fmt::format("no healthy upstream for '{}'", cluster_name)), + true); + return FilterStatus::StopIteration; + } + + ENVOY_STREAM_LOG(debug, "router decoding request", *callbacks_); + + Upstream::HostDescriptionConstSharedPtr host = pool_data->host(); + if (!host) { + return FilterStatus::StopIteration; + } + + if (auto upstream_request = + transaction_info->getUpstreamRequest(host->address()->ip()->addressAsString()); + upstream_request != nullptr) { + // There is action connection, reuse it. + upstream_request_ = upstream_request; + upstream_request_->setDecoderFilterCallbacks(*callbacks_); + ENVOY_STREAM_LOG(debug, "reuse upstream request", *callbacks_); + try { + transaction_info->getTransaction(std::string(metadata->transactionId().value())); + } catch (std::out_of_range const&) { + transaction_info->insertTransaction(std::string(metadata->transactionId().value()), + callbacks_, upstream_request_); + } + } else { + upstream_request_ = std::make_shared(*pool_data, transaction_info); + upstream_request_->setDecoderFilterCallbacks(*callbacks_); + transaction_info->insertUpstreamRequest(host->address()->ip()->addressAsString(), + upstream_request_); + ENVOY_STREAM_LOG(debug, "create new upstream request {}", *callbacks_, + host->address()->ip()->addressAsString()); + + try { + transaction_info->getTransaction(std::string(metadata->transactionId().value())); + } catch (std::out_of_range const&) { + transaction_info->insertTransaction(std::string(metadata->transactionId().value()), + callbacks_, upstream_request_); + } + } + return upstream_request_->start(); + }; + + if (metadata->destination().has_value()) { + auto host = metadata->destination().value(); + if (auto upstream_request = transaction_info->getUpstreamRequest(std::string(host)); + upstream_request != nullptr) { + // There is action connection, reuse it. + ENVOY_STREAM_LOG(debug, "reuse upstream request from EP {}", *callbacks_, host); + upstream_request_ = upstream_request; + + try { + transaction_info->getTransaction(std::string(metadata->transactionId().value())); + } catch (std::out_of_range const&) { + transaction_info->insertTransaction(std::string(metadata->transactionId().value()), + callbacks_, upstream_request_); + } + return upstream_request_->start(); + } else { + ENVOY_STREAM_LOG(debug, "get upstream request for {} failed.", *callbacks_, host); + message_handler_with_loadbalancer(); + } + } else { + ENVOY_STREAM_LOG(debug, "no destination.", *callbacks_); + message_handler_with_loadbalancer(); + } + + return FilterStatus::Continue; +} + +FilterStatus Router::messageEnd() { + // In case pool is not ready, save this into pending_request. + if (upstream_request_->connectionState() != ConnectionState::Connected) { + upstream_request_->addIntoPendingRequest(metadata_); + return FilterStatus::Continue; + } + + Buffer::OwnedImpl transport_buffer; + + // set EP/Opaque, used in upstream + metadata_->setEP(upstream_request_->getLocalIp()); + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, transport_buffer); + + ENVOY_STREAM_LOG(trace, "send buffer : {} bytes\n{}", *callbacks_, transport_buffer.length(), + transport_buffer.toString()); + + upstream_request_->write(transport_buffer, false); + return FilterStatus::Continue; +} + +const Network::Connection* Router::downstreamConnection() const { + if (callbacks_ != nullptr) { + return callbacks_->connection(); + } + + return nullptr; +} + +void Router::cleanup() { upstream_request_.reset(); } + +UpstreamRequest::UpstreamRequest(Upstream::TcpPoolData& pool_data, + std::shared_ptr transaction_info) + : conn_pool_data_(pool_data), transaction_info_(transaction_info), response_complete_(false) {} + +UpstreamRequest::~UpstreamRequest() { + if (conn_pool_handle_) { + conn_pool_handle_->cancel(Tcp::ConnectionPool::CancelPolicy::Default); + } +} + +FilterStatus UpstreamRequest::start() { + if (connectionState() != ConnectionState::NotConnected) { + return FilterStatus::Continue; + } + + ENVOY_LOG(info, "connecting {}", conn_pool_data_.host()->address()->asString()); + + setConnectionState(ConnectionState::Connecting); + conn_state_ = ConnectionState::Connecting; + + Tcp::ConnectionPool::Cancellable* handle = conn_pool_data_.newConnection(*this); + if (handle) { + // Pause while we wait for a connection. + conn_pool_handle_ = handle; + return FilterStatus::Continue; + } + + if (upstream_host_ == nullptr) { + return FilterStatus::StopIteration; + } + + return FilterStatus::Continue; +} + +void UpstreamRequest::releaseConnection(const bool close) { + if (conn_pool_handle_) { + conn_pool_handle_->cancel(Tcp::ConnectionPool::CancelPolicy::Default); + conn_pool_handle_ = nullptr; + } + + setConnectionState(ConnectionState::NotConnected); + + // The event triggered by close will also release this connection so clear conn_data_ before + // closing. + auto conn_data = std::move(conn_data_); + if (close && conn_data != nullptr) { + conn_data->connection().close(Network::ConnectionCloseType::NoFlush); + } +} + +void UpstreamRequest::resetStream() { releaseConnection(true); } + +void UpstreamRequest::onPoolFailure(ConnectionPool::PoolFailureReason reason, absl::string_view, + Upstream::HostDescriptionConstSharedPtr host) { + ENVOY_LOG(info, "on pool failure"); + setConnectionState(ConnectionState::NotConnected); + conn_pool_handle_ = nullptr; + + // Mimic an upstream reset. + onUpstreamHostSelected(host); + UNREFERENCED_PARAMETER(reason); +} + +void UpstreamRequest::onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn_data, + Upstream::HostDescriptionConstSharedPtr host) { + ENVOY_STREAM_LOG(trace, "onPoolReady", *callbacks_); + + conn_data_ = std::move(conn_data); + + onUpstreamHostSelected(host); + conn_data_->addUpstreamCallbacks(*this); + conn_pool_handle_ = nullptr; + + setConnectionState(ConnectionState::Connected); + + onRequestStart(); +} + +void UpstreamRequest::onRequestStart() { + if (!pending_request_.empty()) { + for (const auto& metadata : pending_request_) { + Buffer::OwnedImpl transport_buffer; + + // set EP/Opaque, used in upstream + metadata->setEP(getLocalIp()); + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata, transport_buffer); + + ENVOY_STREAM_LOG(trace, "send buffer : {} bytes\n{}", *callbacks_, transport_buffer.length(), + transport_buffer.toString()); + conn_data_->connection().write(transport_buffer, false); + } + pending_request_.clear(); + } +} + +void UpstreamRequest::onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host) { + upstream_host_ = host; +} + +void UpstreamRequest::onResetStream(ConnectionPool::PoolFailureReason reason) { + switch (reason) { + case ConnectionPool::PoolFailureReason::Overflow: + callbacks_->sendLocalReply( + AppException(AppExceptionType::InternalError, "sip upstream request: too many connections"), + true); + break; + case ConnectionPool::PoolFailureReason::LocalConnectionFailure: + // Should only happen if we closed the connection, due to an error condition, in which case + // we've already handled any possible downstream response. + callbacks_->resetDownstreamConnection(); + break; + case ConnectionPool::PoolFailureReason::RemoteConnectionFailure: + case ConnectionPool::PoolFailureReason::Timeout: + // TODO(zuercher): distinguish between these cases where appropriate (particularly timeout) + // if (!response_started_) { + // callbacks_->sendLocalReply( + // AppException( + // AppExceptionType::InternalError, + // fmt::format("connection failure '{}'", (upstream_host_ != nullptr) + // ? upstream_host_->address()->asString() + // : "to upstream")), + // true); + // return; + //} + + // Error occurred after a partial response, propagate the reset to the downstream. + callbacks_->resetDownstreamConnection(); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +SipFilters::DecoderFilterCallbacks* UpstreamRequest::getTransaction(std::string&& transaction_id) { + try { + return transaction_info_->getTransaction(std::move(transaction_id)).activeTrans(); + } catch (std::out_of_range const&) { + return nullptr; + } +} + +// Tcp::ConnectionPool::UpstreamCallbacks +void UpstreamRequest::onUpstreamData(Buffer::Instance& data, bool end_stream) { + UNREFERENCED_PARAMETER(end_stream); + upstream_buffer_.move(data); + auto response_decoder_ = std::make_unique(*this); + response_decoder_->onData(upstream_buffer_); +} + +void UpstreamRequest::onEvent(Network::ConnectionEvent event) { + ENVOY_LOG(info, "received upstream event {}", event); + switch (event) { + case Network::ConnectionEvent::RemoteClose: + ENVOY_STREAM_LOG(debug, "upstream remote close", *callbacks_); + break; + case Network::ConnectionEvent::LocalClose: + ENVOY_STREAM_LOG(debug, "upstream local close", *callbacks_); + break; + default: + // Connected is consumed by the connection pool. + NOT_REACHED_GCOVR_EXCL_LINE; + } + + releaseConnection(false); +} + +void UpstreamRequest::setDecoderFilterCallbacks(SipFilters::DecoderFilterCallbacks& callbacks) { + callbacks_ = &callbacks; +} + +bool ResponseDecoder::onData(Buffer::Instance& data) { + decoder_->onData(data); + return true; +} + +FilterStatus ResponseDecoder::transportBegin(MessageMetadataSharedPtr metadata) { + ENVOY_LOG(trace, "ResponseDecoder {}", metadata->rawMsg()); + if (metadata->transactionId().has_value()) { + auto transaction_id = metadata->transactionId().value(); + + auto active_trans = parent_.getTransaction(std::string(transaction_id)); + if (active_trans) { + active_trans->startUpstreamResponse(); + active_trans->upstreamData(metadata); + } else { + ENVOY_LOG(debug, "no active trans selected {}\n{}", transaction_id, metadata->rawMsg()); + return FilterStatus::StopIteration; + } + } else { + ENVOY_LOG(debug, "no active trans selected \n{}", metadata->rawMsg()); + return FilterStatus::StopIteration; + } + + return FilterStatus::Continue; +} + +absl::string_view ResponseDecoder::getLocalIp() { return parent_.getLocalIp(); } + +std::string ResponseDecoder::getOwnDomain() { return parent_.transactionInfo()->getOwnDomain(); } + +std::string ResponseDecoder::getDomainMatchParamName() { + return parent_.transactionInfo()->getDomainMatchParamName(); +} + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/router/router_impl.h b/contrib/sip_proxy/filters/network/source/router/router_impl.h new file mode 100644 index 0000000000000..231d94c7a64f4 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/router/router_impl.h @@ -0,0 +1,440 @@ +#pragma once + +#include +#include +#include + +#include "envoy/router/router.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" +#include "envoy/tcp/conn_pool.h" +#include "envoy/thread_local/thread_local.h" +#include "envoy/upstream/load_balancer.h" +#include "envoy/upstream/thread_local_cluster.h" + +#include "source/common/common/logger.h" +#include "source/common/http/header_utility.h" +#include "source/common/upstream/load_balancer_impl.h" + +#include "absl/types/optional.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/route.pb.h" +#include "contrib/sip_proxy/filters/network/source/conn_manager.h" +#include "contrib/sip_proxy/filters/network/source/decoder_events.h" +#include "contrib/sip_proxy/filters/network/source/filters/filter.h" +#include "contrib/sip_proxy/filters/network/source/router/router.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace Router { + +class RouteEntryImplBase : public RouteEntry, + public Route, + public std::enable_shared_from_this { +public: + RouteEntryImplBase(const envoy::extensions::filters::network::sip_proxy::v3alpha::Route& route); + + // Router::RouteEntry + const std::string& clusterName() const override; + const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() const override { + return metadata_match_criteria_.get(); + } + + // Router::Route + const RouteEntry* routeEntry() const override; + + virtual RouteConstSharedPtr matches(MessageMetadata& metadata) const PURE; + +protected: + RouteConstSharedPtr clusterEntry(const MessageMetadata& metadata) const; + bool headersMatch(const Http::HeaderMap& headers) const; + +private: + /* Not used + class DynamicRouteEntry : public RouteEntry, public Route { + public: + DynamicRouteEntry(const RouteEntryImplBase& parent, absl::string_view cluster_name) + : parent_(parent), cluster_name_(std::string(cluster_name)) {} + + // Router::RouteEntry + const std::string& clusterName() const override { return cluster_name_; } + const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() const override { + return parent_.metadataMatchCriteria(); + } + + // Router::Route + const RouteEntry* routeEntry() const override { return this; } + + private: + const RouteEntryImplBase& parent_; + const std::string cluster_name_; + }; */ + + const std::string cluster_name_; + Envoy::Router::MetadataMatchCriteriaConstPtr metadata_match_criteria_; +}; + +using RouteEntryImplBaseConstSharedPtr = std::shared_ptr; + +// match domain from route header or request_uri, this is the more general way +class GeneralRouteEntryImpl : public RouteEntryImplBase { +public: + GeneralRouteEntryImpl( + const envoy::extensions::filters::network::sip_proxy::v3alpha::Route& route); + + // RouteEntryImplBase + RouteConstSharedPtr matches(MessageMetadata& metadata) const override; + +private: + const std::string domain_; +}; + +class RouteMatcher { +public: + RouteMatcher(const envoy::extensions::filters::network::sip_proxy::v3alpha::RouteConfiguration&); + + RouteConstSharedPtr route(MessageMetadata& metadata) const; + +private: + std::vector routes_; +}; + +#define ALL_SIP_ROUTER_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(route_missing) \ + COUNTER(unknown_cluster) \ + COUNTER(upstream_rq_maintenance_mode) \ + COUNTER(no_healthy_upstream) + +struct RouterStats { + ALL_SIP_ROUTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) +}; + +class UpstreamRequest; +class TransactionInfoItem : public Logger::Loggable { +public: + TransactionInfoItem(SipFilters::DecoderFilterCallbacks* active_trans, + std::shared_ptr upstream_request) + : active_trans_(active_trans), upstream_request_(upstream_request) {} + + ~TransactionInfoItem() = default; + + void resetTrans() { active_trans_->onReset(); } + + void appendMessageList(std::shared_ptr message) { messages_.push_back(message); } + + SipFilters::DecoderFilterCallbacks* activeTrans() const { return active_trans_; } + std::shared_ptr upstreamRequest() const { return upstream_request_; } + + SystemTime timestamp() const { return this->active_trans_->streamInfo().startTime(); } + void toDelete() { deleted_ = true; } + bool deleted() { return deleted_; } + +private: + std::list> messages_; + SipFilters::DecoderFilterCallbacks* active_trans_; + std::shared_ptr upstream_request_; + std::chrono::system_clock::time_point timestamp_; + bool deleted_{false}; +}; + +struct ThreadLocalTransactionInfo : public ThreadLocal::ThreadLocalObject, + public Logger::Loggable { + ThreadLocalTransactionInfo(std::shared_ptr parent, Event::Dispatcher& dispatcher, + std::chrono::milliseconds transaction_timeout, std::string own_domain, + std::string domain_match_parameter_name) + : parent_(parent), dispatcher_(dispatcher), transaction_timeout_(transaction_timeout), + own_domain_(own_domain), domain_match_parameter_name_(domain_match_parameter_name) { + audit_timer_ = dispatcher.createTimer([this]() -> void { auditTimerAction(); }); + audit_timer_->enableTimer(std::chrono::seconds(2)); + } + absl::flat_hash_map> transaction_info_map_{}; + absl::flat_hash_map> upstream_request_map_{}; + + std::shared_ptr parent_; + Event::Dispatcher& dispatcher_; + Event::TimerPtr audit_timer_; + std::chrono::milliseconds transaction_timeout_; + std::string own_domain_; + std::string domain_match_parameter_name_; + + void auditTimerAction() { + const auto p1 = dispatcher_.timeSource().systemTime(); + for (auto it = transaction_info_map_.cbegin(); it != transaction_info_map_.cend();) { + if (it->second->deleted()) { + transaction_info_map_.erase(it++); + continue; + } + + auto diff = + std::chrono::duration_cast(p1 - it->second->timestamp()); + if (diff.count() >= transaction_timeout_.count()) { + it->second->resetTrans(); + // transaction_info_map_.erase(it++); + } + + ++it; + /* In single thread, this condition should be cover in line 160 + * And Envoy should be single thread + if (it->second->deleted()) { + transaction_info_map_.erase(it++); + } else { + ++it; + }*/ + } + audit_timer_->enableTimer(std::chrono::seconds(2)); + } +}; + +class TransactionInfo : public std::enable_shared_from_this, + Logger::Loggable { +public: + TransactionInfo(const std::string& cluster_name, ThreadLocal::SlotAllocator& tls, + std::chrono::milliseconds transaction_timeout, std::string own_domain, + std::string domain_match_parameter_name) + : cluster_name_(cluster_name), tls_(tls.allocateSlot()), + transaction_timeout_(transaction_timeout), own_domain_(own_domain), + domain_match_parameter_name_(domain_match_parameter_name) {} + + void init() { + // Note: `this` and `cluster_name` have a a lifetime of the filter. + // That may be shorter than the tls callback if the listener is torn down shortly after it is + // created. We use a weak pointer to make sure this object outlives the tls callbacks. + std::weak_ptr this_weak_ptr = this->shared_from_this(); + tls_->set( + [this_weak_ptr](Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { + if (auto this_shared_ptr = this_weak_ptr.lock()) { + return std::make_shared( + this_shared_ptr, dispatcher, this_shared_ptr->transaction_timeout_, + this_shared_ptr->own_domain_, this_shared_ptr->domain_match_parameter_name_); + } + return nullptr; + }); + + (void)cluster_name_; + } + ~TransactionInfo() = default; + + void insertTransaction(std::string&& transaction_id, + SipFilters::DecoderFilterCallbacks* active_trans, + std::shared_ptr upstream_request) { + tls_->getTyped().transaction_info_map_.emplace(std::make_pair( + transaction_id, std::make_shared(active_trans, upstream_request))); + } + + void deleteTransaction(std::string&& transaction_id) { + tls_->getTyped() + .transaction_info_map_.at(transaction_id) + ->toDelete(); + } + + TransactionInfoItem& getTransaction(std::string&& transaction_id) { + return *(tls_->getTyped().transaction_info_map_.at(transaction_id)); + } + + void insertUpstreamRequest(const std::string& host, + std::shared_ptr upstream_request) { + tls_->getTyped().upstream_request_map_.emplace( + std::make_pair(host, upstream_request)); + } + + std::shared_ptr getUpstreamRequest(const std::string& host) { + try { + return tls_->getTyped().upstream_request_map_.at(host); + } catch (std::out_of_range) { + return nullptr; + } + } + + void deleteUpstreamRequest(const std::string& host) { + tls_->getTyped().upstream_request_map_.erase(host); + } + + std::string getOwnDomain() { return own_domain_; } + + std::string getDomainMatchParamName() { return domain_match_parameter_name_; } + +private: + const std::string cluster_name_; + ThreadLocal::SlotPtr tls_; + std::chrono::milliseconds transaction_timeout_; + std::string own_domain_; + std::string domain_match_parameter_name_; +}; + +class Router : public Upstream::LoadBalancerContextBase, + public virtual DecoderEventHandler, + public SipFilters::DecoderFilter, + Logger::Loggable { +public: + Router(Upstream::ClusterManager& cluster_manager, const std::string& stat_prefix, + Stats::Scope& scope) + : cluster_manager_(cluster_manager), stats_(generateStats(stat_prefix, scope)) {} + + // SipFilters::DecoderFilter + void onDestroy() override; + void setDecoderFilterCallbacks(SipFilters::DecoderFilterCallbacks& callbacks) override; + + // DecoderEventHandler + FilterStatus transportBegin(MessageMetadataSharedPtr metadata) override; + FilterStatus transportEnd() override; + FilterStatus messageBegin(MessageMetadataSharedPtr metadata) override; + FilterStatus messageEnd() override; + + // Upstream::LoadBalancerContext + const Network::Connection* downstreamConnection() const override; + const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() override { + if (route_entry_) { + return route_entry_->metadataMatchCriteria(); + } + return nullptr; + } + + bool shouldSelectAnotherHost(const Upstream::Host& host) override { + if (!metadata_->destination().has_value()) { + return false; + } + return host.address()->ip()->addressAsString() != metadata_->destination().value(); + } + + RouterStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return RouterStats{ALL_SIP_ROUTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), + POOL_GAUGE_PREFIX(scope, prefix), + POOL_HISTOGRAM_PREFIX(scope, prefix))}; + } + + void cleanup(); + + Upstream::ClusterManager& cluster_manager_; + RouterStats stats_; + + RouteConstSharedPtr route_{}; + const RouteEntry* route_entry_{}; + MessageMetadataSharedPtr metadata_{}; + + std::shared_ptr upstream_request_; + SipFilters::DecoderFilterCallbacks* callbacks_{}; + Upstream::ClusterInfoConstSharedPtr cluster_; + std::shared_ptr transaction_infos_{}; + std::shared_ptr settings_; +}; + +class ThreadLocalActiveConn; +class ResponseDecoder : public DecoderCallbacks, + public DecoderEventHandler, + public Logger::Loggable { +public: + ResponseDecoder(UpstreamRequest& parent) + : parent_(parent), decoder_(std::make_unique(*this)) {} + bool onData(Buffer::Instance& data); + + // DecoderEventHandler + FilterStatus messageBegin(MessageMetadataSharedPtr metadata) override { + UNREFERENCED_PARAMETER(metadata); + return FilterStatus::Continue; + } + FilterStatus messageEnd() override { return FilterStatus::Continue; }; + FilterStatus transportBegin(MessageMetadataSharedPtr metadata) override; + FilterStatus transportEnd() override { return FilterStatus::Continue; } + + // DecoderCallbacks + DecoderEventHandler& newDecoderEventHandler(MessageMetadataSharedPtr metadata) override { + UNREFERENCED_PARAMETER(metadata); + return *this; + } + absl::string_view getLocalIp() override; + std::string getOwnDomain() override; + std::string getDomainMatchParamName() override; + +private: + UpstreamRequest& parent_; + DecoderPtr decoder_; +}; + +using ResponseDecoderPtr = std::unique_ptr; + +class UpstreamRequest : public Tcp::ConnectionPool::Callbacks, + public Tcp::ConnectionPool::UpstreamCallbacks, + public std::enable_shared_from_this, + public Logger::Loggable { +public: + UpstreamRequest(Upstream::TcpPoolData& pool_data, + std::shared_ptr transaction_info); + ~UpstreamRequest() override; + FilterStatus start(); + void resetStream(); + void releaseConnection(bool close); + + SipFilters::DecoderFilterCallbacks* getTransaction(std::string&& transaction_id); + + // Tcp::ConnectionPool::Callbacks + void onPoolFailure(ConnectionPool::PoolFailureReason reason, + absl::string_view transport_failure_reason, + Upstream::HostDescriptionConstSharedPtr host) override; + void onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn, + Upstream::HostDescriptionConstSharedPtr host) override; + + void onRequestStart(); + void onRequestComplete(); + void onResponseComplete(); + void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host); + void onResetStream(ConnectionPool::PoolFailureReason reason); + + // Tcp::ConnectionPool::UpstreamCallbacks + void onUpstreamData(Buffer::Instance& data, bool end_stream) override; + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + + void setDecoderFilterCallbacks(SipFilters::DecoderFilterCallbacks& callbacks); + + void addIntoPendingRequest(MessageMetadataSharedPtr metadata) { + if (pending_request_.size() < 1000000) { + pending_request_.push_back(metadata); + } else { + ENVOY_LOG(warn, "pending request is full, drop this request. size {} request {}", + pending_request_.size(), metadata->rawMsg()); + } + } + + ConnectionState connectionState() { return conn_state_; } + void setConnectionState(ConnectionState state) { conn_state_ = state; } + void write(Buffer::Instance& data, bool end_stream) { + return conn_data_->connection().write(data, end_stream); + } + + absl::string_view getLocalIp() { + ENVOY_LOG( + debug, "Local ip: {}", + conn_data_->connection().connectionInfoProvider().localAddress()->ip()->addressAsString()); + return conn_data_->connection() + .connectionInfoProvider() + .localAddress() + ->ip() + ->addressAsString(); + } + + std::shared_ptr transactionInfo() { return transaction_info_; } + +private: + Upstream::TcpPoolData& conn_pool_data_; + + Tcp::ConnectionPool::Cancellable* conn_pool_handle_{}; + Tcp::ConnectionPool::ConnectionDataPtr conn_data_; + Upstream::HostDescriptionConstSharedPtr upstream_host_; + ConnectionState conn_state_{ConnectionState::NotConnected}; + + std::shared_ptr transaction_info_; + SipFilters::DecoderFilterCallbacks* callbacks_{}; + std::list pending_request_; + Buffer::OwnedImpl upstream_buffer_; + + bool request_complete_ : 1; + bool response_complete_ : 1; +}; + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/sip.h b/contrib/sip_proxy/filters/network/source/sip.h new file mode 100644 index 0000000000000..c678573825318 --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/sip.h @@ -0,0 +1,68 @@ +#pragma once + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +enum class HeaderType { + TopLine, + CallId, + Via, + To, + From, + Route, + Contact, + RRoute, + Cseq, + Path, + Event, + SRoute, + WAuth, + Auth, + Other, + InvalidFormat +}; + +enum class MsgType { Request, Response, ErrorMsg }; + +enum class MethodType { + Invite, + Register, + Update, + Refer, + Subscribe, + Notify, + Ack, + Bye, + Cancel, + Ok200, + Failure4xx, + NullMethod +}; + +enum class AppExceptionType { + Unknown = 0, + UnknownMethod = 1, + InvalidMessageType = 2, + WrongMethodName = 3, + BadSequenceId = 4, + MissingResult = 5, + InternalError = 6, + ProtocolError = 7, + InvalidTransform = 8, + InvalidProtocol = 9, + // FBThrift values. + // See https://github.com/facebook/fbthrift/blob/master/thrift/lib/cpp/TApplicationException.h#L52 + UnsupportedClientType = 10, + LoadShedding = 11, + Timeout = 12, + InjectedFailure = 13, + ChecksumMismatch = 14, + Interruption = 15, +}; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/source/stats.h b/contrib/sip_proxy/filters/network/source/stats.h new file mode 100644 index 0000000000000..7077d933ff79b --- /dev/null +++ b/contrib/sip_proxy/filters/network/source/stats.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +/** + * All sip filter stats. @see stats_macros.h + */ +#define ALL_SIP_FILTER_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(cx_destroy_local_with_active_rq) \ + COUNTER(cx_destroy_remote_with_active_rq) \ + COUNTER(request) \ + COUNTER(response) \ + COUNTER(response_error) \ + COUNTER(response_exception) \ + COUNTER(response_reply) \ + COUNTER(response_success) \ + GAUGE(request_active, Accumulate) \ + HISTOGRAM(request_time_ms, Milliseconds) + +/** + * Struct definition for all sip proxy stats. @see stats_macros.h + */ +struct SipFilterStats { + ALL_SIP_FILTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) + + static SipFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return SipFilterStats{ALL_SIP_FILTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), + POOL_GAUGE_PREFIX(scope, prefix), + POOL_HISTOGRAM_PREFIX(scope, prefix))}; + } +}; + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/BUILD b/contrib/sip_proxy/filters/network/test/BUILD new file mode 100644 index 0000000000000..f78a9b9bdafea --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/BUILD @@ -0,0 +1,120 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_mock", + "envoy_cc_test", + "envoy_cc_test_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_mock( + name = "mocks", + srcs = ["mocks.cc"], + hdrs = ["mocks.h"], + deps = [ + "//contrib/sip_proxy/filters/network/source:conn_manager_lib", + "//contrib/sip_proxy/filters/network/source:protocol_interface", + "//contrib/sip_proxy/filters/network/source/filters:factory_base_lib", + "//contrib/sip_proxy/filters/network/source/filters:filter_interface", + "//contrib/sip_proxy/filters/network/source/router:router_interface", + "//test/mocks/network:network_mocks", + "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:printers_lib", + ], +) + +envoy_cc_test_library( + name = "utility_lib", + hdrs = ["utility.h"], + deps = [ + "//contrib/sip_proxy/filters/network/source:sip_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:byte_order_lib", + "//test/common/buffer:utility_lib", + "@envoy_api//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "app_exception_impl_test", + srcs = ["app_exception_impl_test.cc"], + deps = [ + ":mocks", + "//contrib/sip_proxy/filters/network/source:app_exception_lib", + "//test/test_common:printers_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + ":mocks", + "//contrib/sip_proxy/filters/network/source:config", + "//contrib/sip_proxy/filters/network/source/router:config", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:registry_lib", + "@envoy_api//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "conn_manager_test", + srcs = ["conn_manager_test.cc"], + deps = [ + ":mocks", + ":utility_lib", + "//contrib/sip_proxy/filters/network/source:config", + "//contrib/sip_proxy/filters/network/source:conn_manager_lib", + "//contrib/sip_proxy/filters/network/source:decoder_lib", + "//contrib/sip_proxy/filters/network/source/filters:filter_interface", + "//contrib/sip_proxy/filters/network/source/router:config", + "//contrib/sip_proxy/filters/network/source/router:router_interface", + "//test/mocks/network:network_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:printers_lib", + "@envoy_api//contrib/envoy/extensions/filters/network/sip_proxy/v3alpha:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "decoder_test", + srcs = ["decoder_test.cc"], + deps = [ + ":mocks", + ":utility_lib", + "//contrib/sip_proxy/filters/network/source:app_exception_lib", + "//contrib/sip_proxy/filters/network/source:config", + "//contrib/sip_proxy/filters/network/source:conn_manager_lib", + "//contrib/sip_proxy/filters/network/source:decoder_lib", + "//contrib/sip_proxy/filters/network/source/filters:filter_interface", + "//contrib/sip_proxy/filters/network/source/router:config", + "//contrib/sip_proxy/filters/network/source/router:router_interface", + "//test/mocks/network:network_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:printers_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "router_test", + srcs = ["router_test.cc"], + deps = [ + ":mocks", + ":utility_lib", + "//contrib/sip_proxy/filters/network/source:app_exception_lib", + "//contrib/sip_proxy/filters/network/source:config", + "//contrib/sip_proxy/filters/network/source/router:config", + "//contrib/sip_proxy/filters/network/source/router:router_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/upstream:host_mocks", + "//test/test_common:printers_lib", + "//test/test_common:registry_lib", + ], +) diff --git a/contrib/sip_proxy/filters/network/test/app_exception_impl_test.cc b/contrib/sip_proxy/filters/network/test/app_exception_impl_test.cc new file mode 100644 index 0000000000000..9380caff6f2c4 --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/app_exception_impl_test.cc @@ -0,0 +1,24 @@ +#include "source/common/buffer/buffer_impl.h" + +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" +#include "contrib/sip_proxy/filters/network/test/mocks.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +TEST(AppExceptionImplTest, CopyConstructor) { + AppException app_ex(AppExceptionType::InternalError, "msg"); + AppException copy(app_ex); + + EXPECT_EQ(app_ex.type_, copy.type_); + EXPECT_STREQ("msg", copy.what()); +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/config_test.cc b/contrib/sip_proxy/filters/network/test/config_test.cc new file mode 100644 index 0000000000000..940491491108c --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/config_test.cc @@ -0,0 +1,180 @@ +#include + +#include "test/mocks/server/factory_context.h" +#include "test/test_common/registry.h" + +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.validate.h" +#include "contrib/sip_proxy/filters/network/source/config.h" +#include "contrib/sip_proxy/filters/network/source/filters/factory_base.h" +#include "contrib/sip_proxy/filters/network/test/mocks.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +namespace { + +envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy +parseSipProxyFromYaml(const std::string& yaml) { + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy sip_proxy; + TestUtility::loadFromYaml(yaml, sip_proxy); + return sip_proxy; +} +} // namespace + +class SipFilterConfigTestBase { +public: + void testConfig(envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy& config) { + Network::FilterFactoryCb cb; + EXPECT_NO_THROW({ cb = factory_.createFilterFactoryFromProto(config, context_); }); + EXPECT_TRUE(factory_.isTerminalFilterByProto(config, context_)); + + Network::MockConnection connection; + EXPECT_CALL(connection, addReadFilter(_)); + cb(connection); + } + + NiceMock context_; + SipProxyFilterConfigFactory factory_; +}; + +class SipFilterConfigTest : public testing::Test, public SipFilterConfigTestBase {}; + +TEST_F(SipFilterConfigTest, ValidateFail) { + EXPECT_THROW(factory_.createFilterFactoryFromProto( + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy(), context_), + ProtoValidationException); +} + +TEST_F(SipFilterConfigTest, ValidProtoConfiguration) { + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy config{}; + config.set_stat_prefix("my_stat_prefix"); + + testConfig(config); +} + +TEST_F(SipFilterConfigTest, SipProxyWithEmptyProto) { + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy config = + *dynamic_cast( + factory_.createEmptyConfigProto().get()); + config.set_stat_prefix("my_stat_prefix"); + + testConfig(config); +} + +// Test config with an invalid cluster_header. +TEST_F(SipFilterConfigTest, RouterConfigWithValidCluster) { + const std::string yaml = R"EOF( +stat_prefix: sip +route_config: + name: local_route + routes: + match: + domain: A + route: + cluster: A +sip_filters: + - name: envoy.filters.sip.router +)EOF"; + + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy config = + parseSipProxyFromYaml(yaml); + std::string cluster = "A"; + config.mutable_route_config()->mutable_routes()->at(0).mutable_route()->set_cluster(cluster); + EXPECT_NO_THROW({ factory_.createFilterFactoryFromProto(config, context_); }); +} + +// Test config with an explicitly defined router filter. +TEST_F(SipFilterConfigTest, SipProxyWithExplicitRouterConfig) { + const std::string yaml = R"EOF( +stat_prefix: sip +route_config: + name: local_route +sip_filters: + - name: envoy.filters.sip.router +)EOF"; + + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy config = + parseSipProxyFromYaml(yaml); + testConfig(config); +} + +// Test config with an unknown filter. +TEST_F(SipFilterConfigTest, SipProxyWithUnknownFilter) { + const std::string yaml = R"EOF( +stat_prefix: sip +route_config: + name: local_route +sip_filters: + - name: no_such_filter + - name: envoy.filters.sip.router +)EOF"; + + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy config = + parseSipProxyFromYaml(yaml); + + EXPECT_THROW_WITH_REGEX(factory_.createFilterFactoryFromProto(config, context_), EnvoyException, + "no_such_filter"); +} + +// Test config with multiple filters. +TEST_F(SipFilterConfigTest, SipProxyWithMultipleFilters) { + const std::string yaml = R"EOF( +stat_prefix: ingress +route_config: + name: local_route +sip_filters: + - name: envoy.filters.sip.mock_filter + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + key: value + - name: envoy.filters.sip.router +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + + SipFilters::MockFilterConfigFactory factory; + Registry::InjectFactory registry(factory); + + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy config = + parseSipProxyFromYaml(yaml); + testConfig(config); + + EXPECT_EQ(1, factory.config_struct_.fields_size()); + EXPECT_EQ("value", factory.config_struct_.fields().at("key").string_value()); + EXPECT_EQ("sip.ingress.", factory.config_stat_prefix_); +} + +// Test SipProtocolOptions +TEST_F(SipFilterConfigTest, SipProtocolOptions) { + const std::string yaml = R"EOF( +session_affinity: true +registration_affinity: true +)EOF"; + + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProtocolOptions config; + TestUtility::loadFromYaml(yaml, config); + + NiceMock context; + const auto options = std::make_shared(config); + EXPECT_CALL(*context.cluster_manager_.thread_local_cluster_.cluster_.info_, + extensionProtocolOptions(_)) + .WillRepeatedly(Return(options)); + + EXPECT_EQ(true, options->sessionAffinity()); + EXPECT_EQ(true, options->registrationAffinity()); +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/conn_manager_test.cc b/contrib/sip_proxy/filters/network/test/conn_manager_test.cc new file mode 100644 index 0000000000000..6083d2b6394ce --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/conn_manager_test.cc @@ -0,0 +1,724 @@ +#include + +#include "source/common/buffer/buffer_impl.h" + +#include "test/common/stats/stat_test_utility.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/printers.h" + +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.validate.h" +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" +#include "contrib/sip_proxy/filters/network/source/config.h" +#include "contrib/sip_proxy/filters/network/source/conn_manager.h" +#include "contrib/sip_proxy/filters/network/source/encoder.h" +#include "contrib/sip_proxy/filters/network/test/mocks.h" +#include "contrib/sip_proxy/filters/network/test/utility.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +class TestConfigImpl : public ConfigImpl { +public: + TestConfigImpl(envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy proto_config, + Server::Configuration::MockFactoryContext& context, + SipFilters::DecoderFilterSharedPtr decoder_filter, SipFilterStats& stats) + : ConfigImpl(proto_config, context), decoder_filter_(decoder_filter), stats_(stats) {} + + // ConfigImpl + SipFilterStats& stats() override { return stats_; } + + SipFilters::DecoderFilterSharedPtr custom_filter_; + SipFilters::DecoderFilterSharedPtr decoder_filter_; + SipFilterStats& stats_; +}; + +class SipConnectionManagerTest : public testing::Test { +public: + SipConnectionManagerTest() + : stats_(SipFilterStats::generateStats("test.", store_)), + transaction_infos_(std::make_shared()) {} + ~SipConnectionManagerTest() override { + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + } + + void initializeFilter() { initializeFilter(""); } + + void initializeFilter(const std::string& yaml) { + // Destroy any existing filter first. + filter_ = nullptr; + + for (const auto& counter : store_.counters()) { + counter->reset(); + } + + if (yaml.empty()) { + proto_config_.set_stat_prefix("test"); + } else { + TestUtility::loadFromYaml(yaml, proto_config_); + TestUtility::validate(proto_config_); + } + + proto_config_.set_stat_prefix("test"); + + decoder_filter_ = std::make_shared>(); + + config_ = std::make_unique(proto_config_, context_, decoder_filter_, stats_); + EXPECT_EQ(config_->settings()->transactionTimeout(), std::chrono::milliseconds(32000)); + if (custom_filter_) { + config_->custom_filter_ = custom_filter_; + } + + ON_CALL(random_, random()).WillByDefault(Return(42)); + filter_ = std::make_unique( + *config_, random_, filter_callbacks_.connection_.dispatcher_.timeSource(), + transaction_infos_); + filter_->initializeReadFilterCallbacks(filter_callbacks_); + filter_->onNewConnection(); + + // NOP currently. + filter_->onAboveWriteBufferHighWatermark(); + filter_->onBelowWriteBufferLowWatermark(); + } + + void + sendLocalReply(Envoy::Extensions::NetworkFilters::SipProxy::DirectResponse::ResponseType type) { + const std::string yaml = R"EOF( +stat_prefix: egress +route_config: + name: local_route + routes: + - match: + domain: "test" + route: + cluster: "test" +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + initializeFilter(yaml); + MessageMetadata metadata; + const MockDirectResponse response; + EXPECT_CALL(response, encode(_, _)).WillRepeatedly(Return(type)); + filter_->sendLocalReply(metadata, response, true); + } + + void upstreamDataTest() { + const std::string yaml = R"EOF( +stat_prefix: egress +route_config: + name: local_route + routes: + - match: + domain: "test" + route: + cluster: "test" +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + initializeFilter(yaml); + + const std::string SIP_INVITE_WRONG_CONTENT_LENGTH = + "INVITE sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "From: ;tag=1\x0d\x0a" + "To: \x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: \x0d\x0a" + "Supported: 100rel\x0d\x0a" + "Route: \x0d\x0a" + "P-Asserted-Identity: \x0d\x0a" + "Allow: UPDATE,INVITE,ACK,CANCEL,BYE,PRACK,REFER,MESSAGE,INFO\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Type: application/sdp\x0d\x0a" + "Content-Length: 300\x0d\x0a" + "\x0d\x0a" + "v=0\x0d\x0a" + "o=PCTEL 256 2 IN IP4 11.0.0.10\x0d\x0a" + "c=IN IP4 11.0.0.10\x0d\x0a" + "m=audio 4030 RTP/AVP 0 8\x0d\x0a" + "a=rtpmap:0 PCMU/8000\x0d\x0a" + "a=rtpmap:8 PCMU/8000\x0d\x0a"; + + buffer_.add(SIP_INVITE_WRONG_CONTENT_LENGTH); + + // The "Content-Length" is larger to make reassemble do not call complete() + filter_->decoder_->reassemble(buffer_); + filter_->decoder_->metadata_ = std::make_shared(buffer_.toString()); + filter_->decoder_->decode(); + ConnectionManager::ActiveTransPtr trans = + std::make_unique(*filter_, filter_->decoder_->metadata()); + trans->startUpstreamResponse(); + trans->upstreamData(filter_->decoder_->metadata_); + + // TransportBegin + struct MockResponseDecoderTransportBegin : public ConnectionManager::ResponseDecoder { + MockResponseDecoderTransportBegin(ConnectionManager::ActiveTrans& parent) + : ConnectionManager::ResponseDecoder(parent) {} + FilterStatus transportBegin(MessageMetadataSharedPtr) override { + return FilterStatus::StopIteration; + } + }; + MockResponseDecoderTransportBegin decoder_transportBegin(*trans); + trans->response_decoder_ = + std::make_unique(decoder_transportBegin); + trans->upstreamData(filter_->decoder_->metadata_); + + // MessageBegin + struct MockResponseDecoderMessageBegin : public ConnectionManager::ResponseDecoder { + MockResponseDecoderMessageBegin(ConnectionManager::ActiveTrans& parent) + : ConnectionManager::ResponseDecoder(parent) {} + FilterStatus messageBegin(MessageMetadataSharedPtr) override { + return FilterStatus::StopIteration; + } + }; + MockResponseDecoderMessageBegin decoder_messageBegin(*trans); + trans->response_decoder_ = + std::make_unique(decoder_messageBegin); + trans->upstreamData(filter_->decoder_->metadata_); + + // MessageEnd + struct MockResponseDecoderMessageEnd : public ConnectionManager::ResponseDecoder { + MockResponseDecoderMessageEnd(ConnectionManager::ActiveTrans& parent) + : ConnectionManager::ResponseDecoder(parent) {} + FilterStatus messageEnd() override { return FilterStatus::StopIteration; } + }; + MockResponseDecoderMessageEnd decoder_messageEnd(*trans); + trans->response_decoder_ = std::make_unique(decoder_messageEnd); + trans->upstreamData(filter_->decoder_->metadata_); + EXPECT_NE(nullptr, trans->connection()); + + // TransportEnd + struct MockResponseDecoderTransportEnd : public ConnectionManager::ResponseDecoder { + MockResponseDecoderTransportEnd(ConnectionManager::ActiveTrans& parent) + : ConnectionManager::ResponseDecoder(parent) {} + FilterStatus transportEnd() override { return FilterStatus::StopIteration; } + }; + MockResponseDecoderTransportEnd decoder_transportEnd(*trans); + trans->response_decoder_ = + std::make_unique(decoder_transportEnd); + trans->upstreamData(filter_->decoder_->metadata_); + + // AppException + struct MockResponseDecoderAppException : public ConnectionManager::ResponseDecoder { + MockResponseDecoderAppException(ConnectionManager::ActiveTrans& parent) + : ConnectionManager::ResponseDecoder(parent) {} + FilterStatus transportBegin(MessageMetadataSharedPtr) override { + throw AppException(AppExceptionType::ProtocolError, "MockResponseDecoderAppException"); + } + }; + MockResponseDecoderAppException decoder_appException(*trans); + trans->response_decoder_ = + std::make_unique(decoder_appException); + try { + trans->upstreamData(filter_->decoder_->metadata_); + } catch (const EnvoyException& ex) { + filter_->stats_.response_exception_.inc(); + EXPECT_EQ(1U, filter_->stats_.response_exception_.value()); + } + + // EnvoyException + struct MockResponseDecoderEnvoyException : public ConnectionManager::ResponseDecoder { + MockResponseDecoderEnvoyException(ConnectionManager::ActiveTrans& parent) + : ConnectionManager::ResponseDecoder(parent) {} + FilterStatus transportBegin(MessageMetadataSharedPtr) override { + throw EnvoyException("MockResponseDecoderEnvoyException"); + } + }; + MockResponseDecoderEnvoyException decoder_envoyException(*trans); + trans->response_decoder_ = + std::make_unique(decoder_envoyException); + try { + trans->upstreamData(filter_->decoder_->metadata_); + } catch (const EnvoyException& ex) { + filter_->stats_.response_exception_.inc(); + EXPECT_EQ(2U, filter_->stats_.response_exception_.value()); + } + /* + // metadata = nullptr + std::string transid = trans->transactionId(); + trans->metadata_ = nullptr; + filter_->transactions_.emplace(transid, std::move(trans)); + filter_->transactions_.at(transid)->upstreamData(filter_->decoder_->metadata_); + */ + + // transportEnd throw envoyException + filter_->read_callbacks_->connection().setDelayedCloseTimeout(std::chrono::milliseconds(1)); + filter_->read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + ConnectionManager::ActiveTransPtr trans1 = + std::make_unique(*filter_, filter_->decoder_->metadata()); + try { + ConnectionManager::ResponseDecoder response_decoder(*trans1); + response_decoder.newDecoderEventHandler(filter_->decoder_->metadata()); + response_decoder.transportEnd(); + } catch (const EnvoyException& ex) { + filter_->stats_.response_exception_.inc(); + EXPECT_EQ(2U, filter_->stats_.response_exception_.value()); + } + + // end_stream = false + ConnectionManager::ActiveTransPtr trans2 = + std::make_unique(*filter_, filter_->decoder_->metadata()); + trans2->sendLocalReply(AppException(AppExceptionType::ProtocolError, "End_stream is false"), + false); + + // route() with metadata=nullptr; + ConnectionManager::ActiveTransPtr trans3 = + std::make_unique(*filter_, filter_->decoder_->metadata()); + trans3->metadata_ = nullptr; + EXPECT_EQ(nullptr, trans3->route()); + + trans3->resetDownstreamConnection(); + } + + void resetAllTransTest(bool local_reset) { + // int before = stats_.cx_destroy_local_with_active_rq_; + const std::string yaml = R"EOF( +stat_prefix: egress +route_config: + name: local_route + routes: + - match: + domain: "test" + route: + cluster: "test" +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + initializeFilter(yaml); + + const std::string SIP_ACK_FULL = + "ACK sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "From: ;tag=1\x0d\x0a" + "To: \x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 ACK\x0d\x0a" + "Contact: \x0d\x0a" + "Supported: 100rel\x0d\x0a" + "Route: \x0d\x0a" + "P-Asserted-Identity: \x0d\x0a" + "Allow: UPDATE,INVITE,ACK,CANCEL,BYE,PRACK,REFER,MESSAGE,INFO\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Type: application/sdp\x0d\x0a" + "Content-Length: 127\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_ACK_FULL); + + filter_->decoder_->reassemble(buffer_); + filter_->decoder_->metadata_ = std::make_shared(buffer_.toString()); + filter_->decoder_->decode(); + + MessageMetadataSharedPtr metadata = filter_->decoder_->metadata_; + std::string&& k = std::string(metadata->transactionId().value()); + ConnectionManager::ActiveTransPtr new_trans = + std::make_unique(*filter_, metadata); + new_trans->createFilterChain(); + filter_->transactions_.emplace(k, std::move(new_trans)); + filter_->newDecoderEventHandler(metadata); + filter_->resetAllTrans(local_reset); + } + + void resumeResponseTest() { + const std::string yaml = R"EOF( +stat_prefix: egress +route_config: + name: local_route + routes: + - match: + domain: "test" + route: + cluster: "test" +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + initializeFilter(yaml); + + const std::string SIP_ACK_FULL = + "ACK sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "From: ;tag=1\x0d\x0a" + "To: \x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 ACK\x0d\x0a" + "Contact: \x0d\x0a" + "Supported: 100rel\x0d\x0a" + "Route: \x0d\x0a" + "P-Asserted-Identity: \x0d\x0a" + "Allow: UPDATE,INVITE,ACK,CANCEL,BYE,PRACK,REFER,MESSAGE,INFO\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Type: application/sdp\x0d\x0a" + "Content-Length: 127\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_ACK_FULL); + + filter_->decoder_->reassemble(buffer_); + filter_->decoder_->metadata_ = std::make_shared(buffer_.toString()); + filter_->decoder_->decode(); + + MessageMetadataSharedPtr metadata = filter_->decoder_->metadata_; + ConnectionManager::ActiveTransPtr new_trans = + std::make_unique(*filter_, metadata); + + new_trans->filter_action_ = [&](DecoderEventHandler* filter) -> FilterStatus { + UNREFERENCED_PARAMETER(filter); + new_trans->local_response_sent_ = true; + return FilterStatus::StopIteration; + }; + + std::list decoder_filter_list; + ConnectionManager::ActiveTransDecoderFilterPtr wrapper = + std::make_unique(*new_trans, decoder_filter_); + decoder_filter_->setDecoderFilterCallbacks(*wrapper); + LinkedList::moveIntoListBack(std::move(wrapper), decoder_filter_list); + + std::shared_ptr decoder_filter_1 = + std::make_shared>(); + ConnectionManager::ActiveTransDecoderFilterPtr wrapper2 = + std::make_unique(*new_trans, decoder_filter_1); + LinkedList::moveIntoListBack(std::move(wrapper2), decoder_filter_list); + + new_trans->applyDecoderFilters((*(decoder_filter_list.begin())).get()); + + // Other ActiveTransDecoderFilter function cover + ConnectionManager::ActiveTransDecoderFilterPtr decoder = + std::make_unique(*new_trans, decoder_filter_); + EXPECT_EQ(decoder->streamId(), new_trans->streamId()); + EXPECT_EQ(decoder->connection(), new_trans->connection()); + decoder->startUpstreamResponse(); + decoder->streamInfo(); + decoder->upstreamData(metadata); + decoder->resetDownstreamConnection(); + filter_->transactions_.emplace(std::string(metadata->transactionId().value()), + std::move(new_trans)); + decoder->onReset(); + } + + NiceMock context_; + std::shared_ptr decoder_filter_; + Stats::TestUtil::TestStore store_; + SipFilterStats stats_; + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy proto_config_; + + std::unique_ptr config_; + + Buffer::OwnedImpl buffer_; + Buffer::OwnedImpl write_buffer_; + NiceMock filter_callbacks_; + NiceMock random_; + std::unique_ptr filter_; + std::shared_ptr transaction_infos_; + SipFilters::DecoderFilterSharedPtr custom_filter_; + MessageMetadataSharedPtr metadata_; +}; + +TEST_F(SipConnectionManagerTest, OnDataHandlesSipCall) { + const std::string yaml = R"EOF( +stat_prefix: egress +route_config: + name: local_route + routes: + - match: + domain: "test" + route: + cluster: "test" +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + initializeFilter(yaml); + + const std::string SIP_INVITE_FULL = + "INVITE sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "From: ;tag=1\x0d\x0a" + "To: \x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: \x0d\x0a" + "Supported: 100rel\x0d\x0a" + "Route: \x0d\x0a" + "P-Asserted-Identity: \x0d\x0a" + "Allow: UPDATE,INVITE,ACK,CANCEL,BYE,PRACK,REFER,MESSAGE,INFO\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Type: application/sdp\x0d\x0a" + "Content-Length: 127\x0d\x0a" + "\x0d\x0a" + "v=0\x0d\x0a" + "o=PCTEL 256 2 IN IP4 11.0.0.10\x0d\x0a" + "c=IN IP4 11.0.0.10\x0d\x0a" + "m=audio 4030 RTP/AVP 0 8\x0d\x0a" + "a=rtpmap:0 PCMU/8000\x0d\x0a" + "a=rtpmap:8 PCMU/8000\x0d\x0a"; + + buffer_.add(SIP_INVITE_FULL); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(filter_->onData(buffer_, true), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipConnectionManagerTest, OnDataHandlesSipCallEndStream) { + const std::string yaml = R"EOF( +stat_prefix: egress +route_config: + name: local_route + routes: + - match: + domain: "test" + route: + cluster: "test" +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + initializeFilter(yaml); + + const std::string SIP_INVITE_FULL = + "INVITE sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "From: ;tag=1\x0d\x0a" + "To: \x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: \x0d\x0a" + "Supported: 100rel\x0d\x0a" + "Route: \x0d\x0a" + "P-Asserted-Identity: \x0d\x0a" + "Allow: UPDATE,INVITE,ACK,CANCEL,BYE,PRACK,REFER,MESSAGE,INFO\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Type: application/sdp\x0d\x0a" + "Content-Length: 127\x0d\x0a" + "\x0d\x0a" + "v=0\x0d\x0a" + "o=PCTEL 256 2 IN IP4 11.0.0.10\x0d\x0a" + "c=IN IP4 11.0.0.10\x0d\x0a" + "m=audio 4030 RTP/AVP 0 8\x0d\x0a" + "a=rtpmap:0 PCMU/8000\x0d\x0a" + "a=rtpmap:8 PCMU/8000\x0d\x0a"; + + buffer_.add(SIP_INVITE_FULL); + + EXPECT_EQ(filter_->onData(buffer_, true), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipConnectionManagerTest, SendLocalReply_SuccessReply) { + sendLocalReply( + Envoy::Extensions::NetworkFilters::SipProxy::DirectResponse::ResponseType::SuccessReply); +} + +TEST_F(SipConnectionManagerTest, SendLocalReply_ErrorReply) { + sendLocalReply( + Envoy::Extensions::NetworkFilters::SipProxy::DirectResponse::ResponseType::ErrorReply); +} + +TEST_F(SipConnectionManagerTest, SendLocalReply_Exception) { + sendLocalReply( + Envoy::Extensions::NetworkFilters::SipProxy::DirectResponse::ResponseType::Exception); +} + +TEST_F(SipConnectionManagerTest, UpstreamData) { upstreamDataTest(); } + +TEST_F(SipConnectionManagerTest, ResetLocalTrans) { + resetAllTransTest(true); + EXPECT_EQ(1U, store_.counter("test.cx_destroy_local_with_active_rq").value()); +} + +TEST_F(SipConnectionManagerTest, ResetRemoteTrans) { + resetAllTransTest(false); + EXPECT_EQ(1U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); +} +TEST_F(SipConnectionManagerTest, ResumeResponse) { resumeResponseTest(); } + +TEST_F(SipConnectionManagerTest, EncodeInsertEPMatchedxSuri) { + const std::string SIP_OK200_FULL = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: " + "\x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_OK200_FULL); + + absl::string_view header = ""; + metadata_ = std::make_shared(buffer_.toString()); + metadata_->addEPOperation(SIP_OK200_FULL.find("Contact: ") + strlen("Contact: "), header, + "pcsf-cfed.cncs.svc.cluster.local", "x-suri"); + Buffer::OwnedImpl response_buffer; + metadata_->setEP("127.0.0.1"); + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, response_buffer); + EXPECT_EQ(response_buffer.length(), buffer_.length() + strlen(";ep=127.0.0.1")); +} + +TEST_F(SipConnectionManagerTest, EncodeInsertEPMatchedHost) { + const std::string SIP_OK200_FULL = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: " + "\x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_OK200_FULL); + + absl::string_view header = ""; + metadata_ = std::make_shared(buffer_.toString()); + metadata_->addEPOperation(SIP_OK200_FULL.find("Contact: ") + strlen("Contact: "), header, + "11.0.0.10", "host"); + Buffer::OwnedImpl response_buffer; + metadata_->setEP("127.0.0.1"); + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, response_buffer); + EXPECT_EQ(response_buffer.length(), buffer_.length() + strlen(";ep=127.0.0.1")); +} + +TEST_F(SipConnectionManagerTest, EncodeInsertEPNoMatchedxSuri) { + const std::string SIP_OK200_FULL = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: \x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_OK200_FULL); + + absl::string_view header = ""; + metadata_ = std::make_shared(buffer_.toString()); + metadata_->addEPOperation(SIP_OK200_FULL.find("Contact: ") + strlen("Contact: "), header, + "11.0.0.10", "x-suri"); + Buffer::OwnedImpl response_buffer; + metadata_->setEP("127.0.0.1"); + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, response_buffer); + EXPECT_EQ(response_buffer.length(), buffer_.length() + strlen(";ep=127.0.0.1")); +} + +TEST_F(SipConnectionManagerTest, EncodeInsertOpaque) { + const std::string SIP_OK200_FULL = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: " + "\x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_OK200_FULL); + + absl::string_view header = + "Contact: "; + metadata_ = std::make_shared(buffer_.toString()); + metadata_->addOpaqueOperation(SIP_OK200_FULL.find("Contact: "), header); + Buffer::OwnedImpl response_buffer; + metadata_->setEP("127.0.0.1"); + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, response_buffer); + EXPECT_EQ(response_buffer.length(), buffer_.length() + strlen(",opaque=\"127.0.0.1\"")); +} + +TEST_F(SipConnectionManagerTest, EncodeDelete) { + const std::string SIP_OK200_FULL = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: " + "\x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_OK200_FULL); + + metadata_ = std::make_shared(buffer_.toString()); + metadata_->setOperation(Operation(OperationType::Delete, SIP_OK200_FULL.find(";transport="), + DeleteOperationValue(strlen(";transport=TCP")))); + Buffer::OwnedImpl response_buffer; + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, response_buffer); + EXPECT_EQ(response_buffer.length(), buffer_.length() - strlen(";transport=TCP")); +} + +TEST_F(SipConnectionManagerTest, EncodeModify) { + const std::string SIP_OK200_FULL = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: " + "\x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_OK200_FULL); + + metadata_ = std::make_shared(buffer_.toString()); + metadata_->setOperation(Operation(OperationType::Modify, + SIP_OK200_FULL.find(";transport=") + strlen(";transport="), + ModifyOperationValue(strlen("TCP"), "SCTP"))); + Buffer::OwnedImpl response_buffer; + + std::shared_ptr encoder = std::make_shared(); + encoder->encode(metadata_, response_buffer); + EXPECT_EQ(response_buffer.length(), buffer_.length() - strlen("TCP") + strlen("SCTP")); +} + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/decoder_test.cc b/contrib/sip_proxy/filters/network/test/decoder_test.cc new file mode 100644 index 0000000000000..4a25ae8e51501 --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/decoder_test.cc @@ -0,0 +1,565 @@ +#include "source/common/buffer/buffer_impl.h" + +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/test_common/printers.h" +#include "test/test_common/utility.h" + +#include "absl/strings/string_view.h" +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" +#include "contrib/sip_proxy/filters/network/source/config.h" +#include "contrib/sip_proxy/filters/network/source/conn_manager.h" +#include "contrib/sip_proxy/filters/network/source/decoder.h" +#include "contrib/sip_proxy/filters/network/test/mocks.h" +#include "contrib/sip_proxy/filters/network/test/utility.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +class TestConfigImpl : public ConfigImpl { +public: + TestConfigImpl(envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy proto_config, + Server::Configuration::MockFactoryContext& context, + SipFilters::DecoderFilterSharedPtr decoder_filter, SipFilterStats& stats) + : ConfigImpl(proto_config, context), decoder_filter_(decoder_filter), stats_(stats) {} + + // ConfigImpl + SipFilterStats& stats() override { return stats_; } + void createFilterChain(SipFilters::FilterChainFactoryCallbacks& callbacks) override { + if (custom_filter_) { + callbacks.addDecoderFilter(custom_filter_); + } + callbacks.addDecoderFilter(decoder_filter_); + } + + SipFilters::DecoderFilterSharedPtr custom_filter_; + SipFilters::DecoderFilterSharedPtr decoder_filter_; + SipFilterStats& stats_; +}; + +class SipDecoderTest : public testing::Test { +public: + SipDecoderTest() + : stats_(SipFilterStats::generateStats("test.", store_)), + transaction_infos_(std::make_shared()) {} + ~SipDecoderTest() override { + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + } + + void initializeFilter() { initializeFilter(""); } + + void initializeFilter(const std::string& yaml) { + // Destroy any existing filter first. + filter_ = nullptr; + + for (const auto& counter : store_.counters()) { + counter->reset(); + } + + if (yaml.empty()) { + proto_config_.set_stat_prefix("test"); + } else { + TestUtility::loadFromYaml(yaml, proto_config_); + TestUtility::validate(proto_config_); + } + + proto_config_.set_stat_prefix("test"); + + decoder_filter_ = std::make_shared>(); + + config_ = std::make_unique(proto_config_, context_, decoder_filter_, stats_); + if (custom_filter_) { + config_->custom_filter_ = custom_filter_; + } + + ON_CALL(random_, random()).WillByDefault(Return(42)); + filter_ = std::make_unique( + *config_, random_, filter_callbacks_.connection_.dispatcher_.timeSource(), + transaction_infos_); + filter_->initializeReadFilterCallbacks(filter_callbacks_); + filter_->onNewConnection(); + + // NOP currently. + filter_->onAboveWriteBufferHighWatermark(); + filter_->onBelowWriteBufferLowWatermark(); + } + + void headerHandlerTest() { + MockDecoderCallbacks callback; + Decoder decoder(callback); + decoder.setCurrentHeader(HeaderType::Via); + Decoder::REGISTERHandler msgHandler(decoder); + Decoder::HeaderHandler headerHandler(msgHandler); + EXPECT_EQ(HeaderType::Via, headerHandler.currentHeader()); + absl::string_view str(""); + headerHandler.processEvent(str); + headerHandler.processCseq(str); + + DecoderStateMachine::DecoderStatus status(State::MessageBegin); + } + + NiceMock context_; + std::shared_ptr decoder_filter_; + Stats::TestUtil::TestStore store_; + SipFilterStats stats_; + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProxy proto_config_; + + std::unique_ptr config_; + + Buffer::OwnedImpl buffer_; + Buffer::OwnedImpl write_buffer_; + NiceMock filter_callbacks_; + NiceMock random_; + std::unique_ptr filter_; + std::shared_ptr transaction_infos_; + SipFilters::DecoderFilterSharedPtr custom_filter_; +}; + +const std::string yaml = R"EOF( +stat_prefix: egress +route_config: + name: local_route + routes: + - match: + domain: "test" + route: + cluster: "test" +settings: + transaction_timeout: 32s + own_domain: pcsf-cfed.cncs.svc.cluster.local + domain_match_parameter_name: x-suri +)EOF"; + +TEST_F(SipDecoderTest, DecodeINVITE) { + initializeFilter(yaml); + + const std::string SIP_INVITE_FULL = + "INVITE sip:User.0000@tas01.defult.svc.cluster.local;ep=127.0.0.1 SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "Route: \x0d\x0a" + "Record-Route: \x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: \x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_INVITE_FULL); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeRegister) { + initializeFilter(yaml); + + const std::string SIP_REGISTER_FULL = + "REGISTER sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "CSeq: 1 REGISTER\x0d\x0a" + "Contact: \x0d\x0a" + "Expires: 7200\x0d\x0a" + "Supported: 100rel,timer\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "Require: Path\x0d\x0a" + "Path: " + "\x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Authorization: Digest username=\"tc05sub1@cncs.nokialab.com\", realm=\"cncs.nokialab.com\", " + "nonce=\"436dbd0f60a52adc2DPadc43f91c774b51ac4cad614258c43cf9df\", algorithm=MD5, " + "uri=\"sip:10.30.29.47\", response=\"c4f3c2fccdca9c5febc66d4226b5afae\", nc=01201201, " + "cnonce=\"123456\", qop=auth\x0d\x0a" + "Authorization: Digest username=\"tc05sub1@cncs.nokialab.com\", realm=\"cncs.nokialab.com\", " + "nonce=\"436dbd0f60a52adc2DPadc43f91c774b51ac4cad614258c43cf9df\", algorithm=MD5, " + "uri=\"sip:10.30.29.47\", response=\"c4f3c2fccdca9c5febc66d4226b5afae\", nc=01201201, " + "cnonce=\"123456\", qop=auth, opaque=\"127.0.0.1\"\x0d\x0a" + "Authorization: Digest username=\"tc05sub1@cncs.nokialab.com\", realm=\"cncs.nokialab.com\", " + "nonce=\"436dbd0f60a52adc2DPadc43f91c774b51ac4cad614258c43cf9df\", algorithm=MD5, " + "uri=\"sip:10.30.29.47\", response=\"c4f3c2fccdca9c5febc66d4226b5afae\", nc=01201201, " + "cnonce=\"123456\", qop=auth, opaque=\"127.0.0.1\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_REGISTER_FULL); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeOK200) { + initializeFilter(yaml); + + const std::string SIP_OK200_FULL = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 INVITE\x0d\x0a" + "Contact: \x0d\x0a" + "Record-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Service-Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Path: " + "\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_OK200_FULL); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + const std::string SIP_OK200_REGISTER = + "SIP/2.0 200 OK\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "CSeq: 1 Register\x0d\x0a" + "Contact: \x0d\x0a" + "Record-Route: \x0d\x0a" + "Service-Route: \x0d\x0a" + "Route: \x0d\x0a" + "Route: \x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_OK200_REGISTER); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(2U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} +TEST_F(SipDecoderTest, DecodeGeneral) { + initializeFilter(yaml); + + const std::string SIP_CANCEL_FULL = + "CANCEL sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "Route: \x0d\x0a" + "Record-Route: \x0d\x0a" + "CSeq: 1 CANCEL\x0d\x0a" + "Contact: \x0d\x0a" + "Path: " + "\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + + buffer_.add(SIP_CANCEL_FULL); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeSUBSCRIBE) { + initializeFilter(yaml); + + const std::string SIP_SUBSCRIBE_FULL = + "SUBSCRIBE sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "Route: \x0d\x0a" + "Record-Route: \x0d\x0a" + "CSeq: 2 SUBSCRIBE\x0d\x0a" + "Contact: \x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "Event: feature-status-exchange\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_SUBSCRIBE_FULL); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + const std::string SIP_SUBSCRIBE_REG = + "SUBSCRIBE sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "Route: \x0d\x0a" + "Record-Route: \x0d\x0a" + "CSeq: 2 SUBSCRIBE\x0d\x0a" + "Contact: \x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "Event: reg\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_SUBSCRIBE_REG); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(2U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeFAILURE4XX) { + initializeFilter(yaml); + + const std::string SIP_FAILURE4XX_FULL = + "SIP/2.0 401 Unauthorized\x0d\x0a" + "Contact: \x0d\x0a" + "Via: SIP/2.0/TCP " + "192.169.110.15:5060;branch=z9hG4bK1f6eb66cd87d2ae67c4b8a69d67c4f7e60a522a8-0-b-" + "60a52adb349a8674\x0d\x0a" + "Via: SIP/2.0/UDP 127.0.0.1;branch=z9hG4bK_0002_34-139705093266412;lsstag=pt-1.12\x0d\x0a" + "Via: SIP/2.0/UDP 10.30.29.58:38612;received=10.30.29.58;branch=z9hG4bK1434\x0d\x0a" + "From: ;tag=587215\x0d\x0a" + "To: ;tag=182294901\x0d\x0a" + "Call-ID: tc05sub1-1@10.30.29.58-38612\x0d\x0a" + "CSeq: 6 REGISTER\x0d\x0a" + "P-Charging-Vector: icid-value=\"PCSF:1-cfed-0-1-0000000060a52adb-000000000000000b\"\x0d\x0a" + "WWW-Authenticate: Digest " + "realm=\"cncs.nokialab.com\",nonce=" + "\"436dbd0f60a52adc2DPadc43f91c774b51ac4cad614258c43cf9df\",algorithm=MD5,qop=" + "\"auth\"\x0d\x0a" + "WWW-Authenticate: Digest " + "realm=\"cncs.nokialab.com\",nonce=" + "\"436dbd0f60a52adc2DPadc43f91c774b51ac4cad614258c43cf9df\",algorithm=MD5,qop=\"auth\"," + "opaque=\"127.0.0.1\"\x0d\x0a" + "WWW-Authenticate: Digest " + "realm=\"cncs.nokialab.com\",nonce=" + "\"436dbd0f60a52adc2DPadc43f91c774b51ac4cad614258c43cf9df\",algorithm=MD5,qop=\"auth\"," + "opaque=\"127.0.0.1\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_FAILURE4XX_FULL); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeEMPTY) { + initializeFilter(yaml); + + const std::string SIP_EMPTY = "\x0d\x0a"; + buffer_.add(SIP_EMPTY); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + const std::string SIP_WRONG_METHOD_TYPE = + "WRONGMETHOD sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 2 SUBSCRIBE\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_WRONG_METHOD_TYPE); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + const std::string SIP_NO_CONTENT_LENGTH = + "ACK sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 2 ACK\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_NO_CONTENT_LENGTH); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + const std::string SIP_CONTENT_LENGTH_ZERO = + "ACK sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 2 ACK\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: -1\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_NO_CONTENT_LENGTH); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeAck) { + initializeFilter(yaml); + + const std::string SIP_ACK_FULL = + "ACK sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 2 ACK\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_ACK_FULL); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeBYE) { + initializeFilter(yaml); + + const std::string SIP_BYE_FULL = + "BYE sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 2 BYE\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_BYE_FULL); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeUPDATE) { + initializeFilter(yaml); + + const std::string SIP_UPDATE_FULL = + "UPDATE sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 2 UPDATE\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_UPDATE_FULL); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeREFER) { + initializeFilter(yaml); + + const std::string SIP_REFER_FULL = + "REFER sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 2 REFER\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_REFER_FULL); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, DecodeNOTIFY) { + initializeFilter(yaml); + + const std::string SIP_NOTIFY_FULL = + "NOTIFY sip:User.0000@tas01.defult.svc.cluster.local SIP/2.0\x0d\x0a" + "Call-ID: 1-3193@11.0.0.10\x0d\x0a" + "Via: SIP/2.0/TCP 11.0.0.10:15060;branch=z9hG4bK-3193-1-0\x0d\x0a" + "To: \x0d\x0a" + "From: ;tag=1\x0d\x0a" + "Route: \x0d\x0a" + "CSeq: 1 NOTIFY\x0d\x0a" + "Contact: ;tag=1\x0d\x0a" + "Record-Route: \x0d\x0a" + "Service-Route: \x0d\x0a" + "Path: " + "\x0d\x0a" + "Max-Forwards: 70\x0d\x0a" + "Content-Length: 0\x0d\x0a" + "\x0d\x0a"; + buffer_.add(SIP_NOTIFY_FULL); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(SipDecoderTest, HandleState) { + MessageMetadataSharedPtr metadata; + MockDecoderEventHandler handler; + DecoderStateMachine machine(metadata, handler); + /* TODO panic: not reached + machine.setCurrentState(State::WaitForData); + */ + machine.setCurrentState(State::MessageEnd); + EXPECT_CALL(handler, messageEnd()).WillOnce(Return(FilterStatus::StopIteration)); + machine.run(); + EXPECT_EQ(State::TransportEnd, machine.currentState()); +} + +TEST_F(SipDecoderTest, HeaderTest) { + StateNameValues stateNameValues_; + EXPECT_EQ("Done", stateNameValues_.name(State::Done)); +} + +TEST_F(SipDecoderTest, HeaderHandlerTest) { headerHandlerTest(); } + +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/mocks.cc b/contrib/sip_proxy/filters/network/test/mocks.cc new file mode 100644 index 0000000000000..2fa4e2ae1bc6f --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/mocks.cc @@ -0,0 +1,97 @@ +#include "contrib/sip_proxy/filters/network/test/mocks.h" + +#include + +#include "source/common/protobuf/protobuf.h" + +#include "gtest/gtest.h" + +using testing::_; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { + +// Provide a specialization for ProtobufWkt::Struct (for MockFilterConfigFactory) +template <> +void MessageUtil::validate(const ProtobufWkt::Struct&, ProtobufMessage::ValidationVisitor&) {} + +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +MockConfig::MockConfig() = default; +MockConfig::~MockConfig() = default; + +MockDecoderCallbacks::MockDecoderCallbacks() { + ON_CALL(*this, getLocalIp()).WillByDefault(Return("127.0.0.1")); + ON_CALL(*this, getOwnDomain()).WillByDefault(Return("pcsf-cfed.cncs.svc.cluster.local")); + ON_CALL(*this, getDomainMatchParamName()).WillByDefault(Return("x-suri")); +} +MockDecoderCallbacks::~MockDecoderCallbacks() = default; + +MockDecoderEventHandler::MockDecoderEventHandler() { + ON_CALL(*this, transportBegin(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, transportEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, messageBegin(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, messageEnd()).WillByDefault(Return(FilterStatus::Continue)); +} +MockDecoderEventHandler::~MockDecoderEventHandler() = default; + +MockDirectResponse::MockDirectResponse() = default; +MockDirectResponse::~MockDirectResponse() = default; + +namespace SipFilters { + +MockDecoderFilter::MockDecoderFilter() { + ON_CALL(*this, transportBegin(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, transportEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, messageBegin(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, messageEnd()).WillByDefault(Return(FilterStatus::Continue)); +} +MockDecoderFilter::~MockDecoderFilter() = default; + +MockDecoderFilterCallbacks::MockDecoderFilterCallbacks() { + + ON_CALL(*this, streamId()).WillByDefault(Return(stream_id_)); + ON_CALL(*this, transactionInfos()).WillByDefault(Return(transaction_infos_)); + ON_CALL(*this, streamInfo()).WillByDefault(ReturnRef(stream_info_)); +} +MockDecoderFilterCallbacks::~MockDecoderFilterCallbacks() = default; + +MockFilterConfigFactory::MockFilterConfigFactory() : name_("envoy.filters.sip.mock_filter") { + mock_filter_ = std::make_shared>(); +} + +MockFilterConfigFactory::~MockFilterConfigFactory() = default; + +FilterFactoryCb MockFilterConfigFactory::createFilterFactoryFromProto( + const Protobuf::Message& proto_config, const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) { + UNREFERENCED_PARAMETER(context); + + config_struct_ = dynamic_cast(proto_config); + config_stat_prefix_ = stats_prefix; + + return [this](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addDecoderFilter(mock_filter_); + }; +} + +} // namespace SipFilters +// +namespace Router { + +MockRouteEntry::MockRouteEntry() { + ON_CALL(*this, clusterName()).WillByDefault(ReturnRef(cluster_name_)); +} +MockRouteEntry::~MockRouteEntry() = default; + +MockRoute::MockRoute() { ON_CALL(*this, routeEntry()).WillByDefault(Return(&route_entry_)); } +MockRoute::~MockRoute() = default; + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/mocks.h b/contrib/sip_proxy/filters/network/test/mocks.h new file mode 100644 index 0000000000000..456d973e78ee8 --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/mocks.h @@ -0,0 +1,180 @@ +#pragma once + +#include "envoy/router/router.h" + +#include "test/mocks/network/mocks.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/printers.h" + +#include "contrib/sip_proxy/filters/network/source/conn_manager.h" +#include "contrib/sip_proxy/filters/network/source/conn_state.h" +#include "contrib/sip_proxy/filters/network/source/filters/factory_base.h" +#include "contrib/sip_proxy/filters/network/source/filters/filter.h" +#include "contrib/sip_proxy/filters/network/source/metadata.h" +#include "contrib/sip_proxy/filters/network/source/protocol.h" +#include "contrib/sip_proxy/filters/network/source/router/router.h" +#include "gmock/gmock.h" + +using testing::NiceMock; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { + +class MockConfig : public Config { +public: + MockConfig(); + ~MockConfig() override; + + // SipProxy::Config + MOCK_METHOD(SipFilters::FilterChainFactory&, filterFactory, ()); + MOCK_METHOD(SipFilterStats&, stats, ()); + MOCK_METHOD(DecoderPtr, createDecoder, (DecoderCallbacks&)); + MOCK_METHOD(Router::Config&, routerConfig, ()); +}; + +class MockDecoderEventHandler : public DecoderEventHandler { +public: + MockDecoderEventHandler(); + ~MockDecoderEventHandler() override; + + // SipProxy::DecoderEventHandler + MOCK_METHOD(FilterStatus, transportBegin, (MessageMetadataSharedPtr metadata)); + MOCK_METHOD(FilterStatus, transportEnd, ()); + MOCK_METHOD(FilterStatus, messageBegin, (MessageMetadataSharedPtr metadata)); + MOCK_METHOD(FilterStatus, messageEnd, ()); +}; + +class MockDecoderCallbacks : public DecoderCallbacks { +public: + MockDecoderCallbacks(); + ~MockDecoderCallbacks() override; + + // SipProxy::DecoderCallbacks + MOCK_METHOD(DecoderEventHandler&, newDecoderEventHandler, (MessageMetadataSharedPtr)); + MOCK_METHOD(absl::string_view, getLocalIp, ()); + MOCK_METHOD(std::string, getOwnDomain, ()); + MOCK_METHOD(std::string, getDomainMatchParamName, ()); +}; + +class MockDirectResponse : public DirectResponse { +public: + MockDirectResponse(); + ~MockDirectResponse() override; + + // SipProxy::DirectResponse + MOCK_METHOD(DirectResponse::ResponseType, encode, + (MessageMetadata & metadata, Buffer::Instance& buffer), (const)); +}; + +namespace Router { +class MockRoute; +} // namespace Router + +namespace SipFilters { + +class MockDecoderFilter : public DecoderFilter { +public: + MockDecoderFilter(); + ~MockDecoderFilter() override; + + // SipProxy::SipFilters::DecoderFilter + MOCK_METHOD(void, onDestroy, ()); + MOCK_METHOD(void, setDecoderFilterCallbacks, (DecoderFilterCallbacks & callbacks)); + MOCK_METHOD(void, resetUpstreamConnection, ()); + MOCK_METHOD(bool, passthroughSupported, (), (const)); + + // SipProxy::DecoderEventHandler + MOCK_METHOD(FilterStatus, passthroughData, (Buffer::Instance & data)); + MOCK_METHOD(FilterStatus, transportBegin, (MessageMetadataSharedPtr metadata)); + MOCK_METHOD(FilterStatus, transportEnd, ()); + MOCK_METHOD(FilterStatus, messageBegin, (MessageMetadataSharedPtr metadata)); + MOCK_METHOD(FilterStatus, messageEnd, ()); +}; + +class MockDecoderFilterCallbacks : public DecoderFilterCallbacks { +public: + MockDecoderFilterCallbacks(); + ~MockDecoderFilterCallbacks() override; + + // SipProxy::SipFilters::DecoderFilterCallbacks + MOCK_METHOD(uint64_t, streamId, (), (const)); + MOCK_METHOD(std::string, transactionId, (), (const)); + MOCK_METHOD(const Network::Connection*, connection, (), (const)); + MOCK_METHOD(Router::RouteConstSharedPtr, route, ()); + MOCK_METHOD(Event::Dispatcher&, dispatcher, ()); + MOCK_METHOD(void, sendLocalReply, (const DirectResponse&, bool)); + MOCK_METHOD(void, startUpstreamResponse, ()); + MOCK_METHOD(ResponseStatus, upstreamData, (MessageMetadataSharedPtr)); + MOCK_METHOD(void, resetDownstreamConnection, ()); + MOCK_METHOD(StreamInfo::StreamInfo&, streamInfo, ()); + MOCK_METHOD(std::shared_ptr, transactionInfos, ()); + MOCK_METHOD(std::shared_ptr, settings, ()); + MOCK_METHOD(void, onReset, ()); + MOCK_METHOD(MessageMetadataSharedPtr, responseMetadata, ()); + MOCK_METHOD(bool, responseSuccess, ()); + + uint64_t stream_id_{1}; + std::string transaction_id_{"test"}; + NiceMock connection_; + NiceMock stream_info_; + std::shared_ptr route_; + std::shared_ptr transaction_infos_; +}; + +class MockFilterConfigFactory : public NamedSipFilterConfigFactory { +public: + MockFilterConfigFactory(); + ~MockFilterConfigFactory() override; + + FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& proto_config, + const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return name_; } + + ProtobufWkt::Struct config_struct_; + std::string config_stat_prefix_; + +private: + std::shared_ptr mock_filter_; + const std::string name_; +}; + +} // namespace SipFilters + +namespace Router { + +class MockRouteEntry : public RouteEntry { +public: + MockRouteEntry(); + ~MockRouteEntry() override; + + // // SipProxy::Router::RouteEntry + MOCK_METHOD(const std::string&, clusterName, (), (const)); + MOCK_METHOD(const Envoy::Router::MetadataMatchCriteria*, metadataMatchCriteria, (), (const)); + std::string cluster_name_{"fake_cluster"}; +}; + +class MockRoute : public Route { +public: + MockRoute(); + ~MockRoute() override; + + // SipProxy::Router::Route + MOCK_METHOD(const RouteEntry*, routeEntry, (), (const)); + + NiceMock route_entry_; +}; + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/router_test.cc b/contrib/sip_proxy/filters/network/test/router_test.cc new file mode 100644 index 0000000000000..41064a1abbbc2 --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/router_test.cc @@ -0,0 +1,682 @@ +#include +#include + +#include "envoy/tcp/conn_pool.h" + +#include "source/common/buffer/buffer_impl.h" + +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/upstream/host.h" +#include "test/test_common/printers.h" +#include "test/test_common/registry.h" + +#include "contrib/sip_proxy/filters/network/source/app_exception_impl.h" +#include "contrib/sip_proxy/filters/network/source/config.h" +#include "contrib/sip_proxy/filters/network/source/router/config.h" +#include "contrib/sip_proxy/filters/network/source/router/router_impl.h" +#include "contrib/sip_proxy/filters/network/source/sip.h" +#include "contrib/sip_proxy/filters/network/test/mocks.h" +#include "contrib/sip_proxy/filters/network/test/utility.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::ContainsRegex; +using testing::Eq; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace Router { + +class SipRouterTest : public testing::Test { +public: + SipRouterTest() = default; + void initializeTrans(bool has_option = true) { + if (has_option == true) { + const std::string yaml = R"EOF( +session_affinity: true +registration_affinity: true +)EOF"; + + envoy::extensions::filters::network::sip_proxy::v3alpha::SipProtocolOptions config; + TestUtility::loadFromYaml(yaml, config); + + const auto options = std::make_shared(config); + EXPECT_CALL(*context_.cluster_manager_.thread_local_cluster_.cluster_.info_, + extensionProtocolOptions(_)) + .WillRepeatedly(Return(options)); + } + + transaction_infos_ = std::make_shared(); + context_.cluster_manager_.initializeThreadLocalClusters({"cluster"}); + } + + void initializeRouter() { + route_ = new NiceMock(); + route_ptr_.reset(route_); + + router_ = std::make_unique(context_.clusterManager(), "test", context_.scope()); + + EXPECT_EQ(nullptr, router_->downstreamConnection()); + + EXPECT_CALL(callbacks_, transactionInfos()).WillOnce(Return(transaction_infos_)); + router_->setDecoderFilterCallbacks(callbacks_); + } + + void initializeRouterWithCallback() { + route_ = new NiceMock(); + route_ptr_.reset(route_); + + router_ = std::make_unique(context_.clusterManager(), "test", context_.scope()); + + EXPECT_CALL(callbacks_, transactionInfos()).WillOnce(Return(transaction_infos_)); + router_->setDecoderFilterCallbacks(callbacks_); + + EXPECT_EQ(nullptr, router_->downstreamConnection()); + } + + void initializeMetadata(MsgType msg_type, MethodType method = MethodType::Invite, + bool set_destination = true) { + + metadata_ = std::make_shared(); + metadata_->setMethodType(method); + metadata_->setMsgType(msg_type); + metadata_->setTransactionId(""); + metadata_->setRouteEP("10.0.0.1"); + metadata_->setRouteOpaque("10.0.0.1"); + metadata_->setDomain( + "", + "host"); + if (set_destination) { + metadata_->setDestination("10.0.0.1"); + } + } + + void initializeTransaction() { + auto transaction_info_ptr = std::make_shared( + "test", thread_local_, static_cast(2), "", "x-suri"); + transaction_info_ptr->init(); + transaction_infos_->emplace(cluster_name_, transaction_info_ptr); + } + + void startRequest(MsgType msg_type, MethodType method = MethodType::Invite) { + // const bool strip_service_name = false) + initializeMetadata(msg_type, method); + EXPECT_EQ(FilterStatus::Continue, router_->transportBegin(metadata_)); + + EXPECT_CALL(callbacks_, route()).WillRepeatedly(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillRepeatedly(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); + + EXPECT_CALL(callbacks_, connection()).WillRepeatedly(Return(&connection_)); + + EXPECT_CALL(callbacks_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); + EXPECT_EQ(&connection_, router_->downstreamConnection()); + + EXPECT_EQ(nullptr, router_->metadataMatchCriteria()); + } + + void connectUpstream() { + EXPECT_CALL(*context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.connection_data_, + addUpstreamCallbacks(_)) + .WillOnce(Invoke([&](Tcp::ConnectionPool::UpstreamCallbacks& cb) -> void { + upstream_callbacks_ = &cb; + })); + + conn_state_.reset(); + EXPECT_CALL(*context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.connection_data_, + connectionState()) + .WillRepeatedly( + Invoke([&]() -> Tcp::ConnectionPool::ConnectionState* { return conn_state_.get(); })); + + context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.poolReady(upstream_connection_); + + EXPECT_NE(nullptr, upstream_callbacks_); + } + + void startRequestWithExistingConnection(MsgType msg_type, + MethodType method = MethodType::Invite) { + initializeMetadata(msg_type, method); + EXPECT_EQ(FilterStatus::Continue, router_->transportBegin({})); + + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); + EXPECT_NE(nullptr, upstream_callbacks_); + } + + void completeRequest() { + EXPECT_EQ(FilterStatus::Continue, router_->messageEnd()); + EXPECT_EQ(FilterStatus::Continue, router_->transportEnd()); + } + + void returnResponse(MsgType msg_type = MsgType::Response, bool is_success = true) { + Buffer::OwnedImpl buffer; + + initializeMetadata(msg_type, MethodType::Ok200, false); + + ON_CALL(callbacks_, responseSuccess()).WillByDefault(Return(is_success)); + + upstream_callbacks_->onUpstreamData(buffer, false); + } + + void destroyRouter() { + router_->onDestroy(); + router_.reset(); + } + void destroyRouterOutofRange() { + // std::out_of_range Exception + EXPECT_CALL(callbacks_, transactionId()) + .Times(2) + .WillOnce(Return("test")) + .WillOnce(Return("test1")); + + router_->onDestroy(); + router_.reset(); + } + + NiceMock context_; + NiceMock connection_; + NiceMock streamInfo_; + NiceMock dispatcher_; + NiceMock time_source_; + NiceMock callbacks_; + NiceMock* route_{}; + NiceMock route_entry_; + NiceMock* host_{}; + Tcp::ConnectionPool::ConnectionStatePtr conn_state_; + Buffer::OwnedImpl buffer_; + NiceMock thread_local_; + + std::shared_ptr transaction_infos_; + + RouteConstSharedPtr route_ptr_; + std::unique_ptr router_; + + std::string cluster_name_{"cluster"}; + + MsgType msg_type_{MsgType::Request}; + MessageMetadataSharedPtr metadata_; + + Tcp::ConnectionPool::UpstreamCallbacks* upstream_callbacks_{}; + NiceMock upstream_connection_; +}; + +TEST_F(SipRouterTest, Call) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + connectUpstream(); + completeRequest(); + returnResponse(); + EXPECT_CALL(callbacks_, transactionId()).WillRepeatedly(Return("test")); + destroyRouter(); +} + +TEST_F(SipRouterTest, CallWithNotify) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + + initializeMetadata(MsgType::Request, MethodType::Notify); + metadata_->setEP("10.0.0.1"); + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); + + auto& transaction_info_ptr = (*transaction_infos_)[cluster_name_]; + EXPECT_NE(nullptr, transaction_info_ptr); + std::shared_ptr upstream_request_ptr = + transaction_info_ptr->getUpstreamRequest("10.0.0.1"); + EXPECT_NE(nullptr, upstream_request_ptr); + upstream_request_ptr->resetStream(); + + transaction_info_ptr->deleteUpstreamRequest("10.0.0.1"); + upstream_request_ptr = transaction_info_ptr->getUpstreamRequest("10.0.0.1"); + EXPECT_EQ(nullptr, upstream_request_ptr); +} + +TEST_F(SipRouterTest, DiffRouter) { + initializeTrans(false); + initializeRouter(); + router_->metadataMatchCriteria(); + EXPECT_EQ(nullptr, router_->metadataMatchCriteria()); + initializeTransaction(); + EXPECT_EQ(router_->metadataMatchCriteria(), route_entry_.metadataMatchCriteria()); + startRequest(MsgType::Request); + + initializeRouter(); + startRequest(MsgType::Request); +} + +TEST_F(SipRouterTest, DiffRouterDiffTrans) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + + initializeRouter(); + + initializeMetadata(MsgType::Request, MethodType::Invite); + EXPECT_EQ(FilterStatus::Continue, router_->transportBegin(metadata_)); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + metadata_->setTransactionId("cluster"); + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); +} + +TEST_F(SipRouterTest, DiffDestination) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + initializeMetadata(MsgType::Request, MethodType::Register); + metadata_->setEP("10.0.0.1"); + EXPECT_EQ(FilterStatus::StopIteration, router_->messageBegin(metadata_)); + + initializeRouter(); + initializeMetadata(MsgType::Request, MethodType::Register); + metadata_->setEP("10.0.0.1"); + metadata_->setDestination("10.0.0.1"); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); +} + +TEST_F(SipRouterTest, DiffDestinationDiffTrans) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + + initializeRouter(); + initializeMetadata(MsgType::Request, MethodType::Ack); + metadata_->setDestination("10.0.0.1"); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + metadata_->setTransactionId("cluster"); + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); +} + +TEST_F(SipRouterTest, DiffDestinationNoTrans) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + + initializeRouter(); + initializeMetadata(MsgType::Request, MethodType::Ack); + metadata_->setDestination("10.0.0.1"); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + metadata_->setTransactionId(""); + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); +} + +TEST_F(SipRouterTest, NoDestination) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + + initializeMetadata(MsgType::Request, MethodType::Invite, false); + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); +} + +TEST_F(SipRouterTest, CallNoRouter) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + initializeMetadata(MsgType::Request); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(nullptr)); + EXPECT_CALL(callbacks_, sendLocalReply(_, _)) + .WillOnce(Invoke([&](const DirectResponse& response, bool end_stream) -> void { + auto& app_ex = dynamic_cast(response); + EXPECT_EQ(AppExceptionType::UnknownMethod, app_ex.type_); + EXPECT_THAT(app_ex.what(), ContainsRegex(".*no route.*")); + EXPECT_TRUE(end_stream); + })); + EXPECT_EQ(FilterStatus::StopIteration, router_->messageBegin(metadata_)); + EXPECT_EQ(1U, context_.scope().counterFromString("test.route_missing").value()); + + destroyRouterOutofRange(); +} + +TEST_F(SipRouterTest, CallNoCluster) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + initializeMetadata(MsgType::Request); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + EXPECT_CALL(context_.cluster_manager_, getThreadLocalCluster(Eq(cluster_name_))) + .WillOnce(Return(nullptr)); + EXPECT_CALL(callbacks_, sendLocalReply(_, _)) + .WillOnce(Invoke([&](const DirectResponse& response, bool end_stream) -> void { + auto& app_ex = dynamic_cast(response); + EXPECT_EQ(AppExceptionType::InternalError, app_ex.type_); + EXPECT_THAT(app_ex.what(), ContainsRegex(".*unknown cluster.*")); + EXPECT_TRUE(end_stream); + })); + EXPECT_EQ(FilterStatus::StopIteration, router_->messageBegin(metadata_)); + EXPECT_EQ(1U, context_.scope().counterFromString("test.unknown_cluster").value()); + + destroyRouter(); +} + +TEST_F(SipRouterTest, ClusterMaintenanceMode) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + initializeMetadata(MsgType::Request); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + EXPECT_CALL(*context_.cluster_manager_.thread_local_cluster_.cluster_.info_, maintenanceMode()) + .WillOnce(Return(true)); + + EXPECT_CALL(callbacks_, sendLocalReply(_, _)) + .WillOnce(Invoke([&](const DirectResponse& response, bool end_stream) -> void { + auto& app_ex = dynamic_cast(response); + EXPECT_EQ(AppExceptionType::InternalError, app_ex.type_); + EXPECT_THAT(app_ex.what(), ContainsRegex(".*maintenance mode.*")); + EXPECT_TRUE(end_stream); + })); + EXPECT_EQ(FilterStatus::StopIteration, router_->messageBegin(metadata_)); + EXPECT_EQ(1U, context_.scope().counterFromString("test.upstream_rq_maintenance_mode").value()); + destroyRouter(); +} + +TEST_F(SipRouterTest, NoHealthyHosts) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + initializeMetadata(MsgType::Request); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillOnce(ReturnRef(cluster_name_)); + EXPECT_CALL(context_.cluster_manager_.thread_local_cluster_, tcpConnPool(_, _)) + .WillOnce(Return(absl::nullopt)); + + EXPECT_CALL(callbacks_, sendLocalReply(_, _)) + .WillOnce(Invoke([&](const DirectResponse& response, bool end_stream) -> void { + auto& app_ex = dynamic_cast(response); + EXPECT_EQ(AppExceptionType::InternalError, app_ex.type_); + EXPECT_THAT(app_ex.what(), ContainsRegex(".*no healthy upstream.*")); + EXPECT_TRUE(end_stream); + })); + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); + EXPECT_EQ(1U, context_.scope().counterFromString("test.no_healthy_upstream").value()); + destroyRouter(); +} + +TEST_F(SipRouterTest, NoHost) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + initializeMetadata(MsgType::Request); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillOnce(ReturnRef(cluster_name_)); + + EXPECT_CALL(context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_, host()) + .WillOnce(Return(nullptr)); + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); + destroyRouter(); +} + +TEST_F(SipRouterTest, NoNewConnection) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + initializeMetadata(MsgType::Request); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillOnce(ReturnRef(cluster_name_)); + + EXPECT_CALL(context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_, newConnection(_)) + .WillOnce(Return(nullptr)); + + EXPECT_EQ(FilterStatus::Continue, router_->messageBegin(metadata_)); + destroyRouter(); +} + +TEST_F(SipRouterTest, CallWithExistingConnection) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + connectUpstream(); + completeRequest(); + returnResponse(); + metadata_->setDestination("10.0.0.1"); + router_->cleanup(); + startRequestWithExistingConnection(MsgType::Request); + destroyRouter(); +} + +TEST_F(SipRouterTest, PoolFailure) { + initializeTrans(); + initializeRouterWithCallback(); + initializeTransaction(); + startRequest(MsgType::Request); + // connectUpstream(); + context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.poolFailure( + ConnectionPool::PoolFailureReason::RemoteConnectionFailure); + completeRequest(); +} + +TEST_F(SipRouterTest, NewConnectionFailure) { + initializeTrans(); + initializeRouterWithCallback(); + initializeTransaction(); + EXPECT_CALL(context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_, newConnection(_)) + .WillOnce( + Invoke([&](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { + context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.newConnectionImpl(cb); + context_.cluster_manager_.thread_local_cluster_.tcp_conn_pool_.poolReady( + upstream_connection_); + return nullptr; + })); + startRequest(MsgType::Request); +} + +TEST_F(SipRouterTest, UpstreamCloseMidResponse) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + connectUpstream(); + + upstream_callbacks_->onEvent(Network::ConnectionEvent::LocalClose); + upstream_callbacks_->onEvent(Network::ConnectionEvent::RemoteClose); + // Panic: NOT_REACHED_GCOVR_EXCL_LINE + // upstream_callbacks_->onEvent(static_cast(9999)); +} + +TEST_F(SipRouterTest, RouteEntryImplBase) { + const envoy::extensions::filters::network::sip_proxy::v3alpha::Route route; + GeneralRouteEntryImpl* base = new GeneralRouteEntryImpl(route); + EXPECT_EQ("", base->clusterName()); + EXPECT_EQ(base, base->routeEntry()); + EXPECT_EQ(nullptr, base->metadataMatchCriteria()); +} + +envoy::extensions::filters::network::sip_proxy::v3alpha::RouteConfiguration +parseConfigFromYaml(const std::string& yaml) { + envoy::extensions::filters::network::sip_proxy::v3alpha::RouteConfiguration route; + TestUtility::loadFromYaml(yaml, route); + return route; +} + +TEST_F(SipRouterTest, RouteMatcher) { + + const std::string yaml = R"EOF( + name: local_route + routes: + match: + domain: pcsf-cfed.cncs.svc.cluster.local + route: + cluster: A +)EOF"; + + envoy::extensions::filters::network::sip_proxy::v3alpha::RouteConfiguration config; + TestUtility::loadFromYaml(yaml, config); + + initializeMetadata(MsgType::Request); + auto matcher_ptr = std::make_shared(config); + + // Match domain + metadata_->setDomain("", + "x-suri"); + matcher_ptr->route(*metadata_); + + // Not match domain + metadata_->setDomain( + "", + "x-suri"); + matcher_ptr->route(*metadata_); +} + +TEST_F(SipRouterTest, HandlePendingRequest) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + connectUpstream(); + completeRequest(); + + auto& transaction_info_ptr = (*transaction_infos_)[cluster_name_]; + EXPECT_NE(nullptr, transaction_info_ptr); + std::shared_ptr upstream_request_ptr = + transaction_info_ptr->getUpstreamRequest("10.0.0.1"); + EXPECT_NE(nullptr, upstream_request_ptr); + upstream_request_ptr->addIntoPendingRequest(metadata_); + // trigger full + upstream_request_ptr->onRequestStart(); + + for (int i = 0; i < 1000003; i++) { + upstream_request_ptr->addIntoPendingRequest(metadata_); + } + + upstream_request_ptr->resetStream(); + + // Other UpstreamRequest in definition + upstream_request_ptr->onAboveWriteBufferHighWatermark(); + upstream_request_ptr->onBelowWriteBufferLowWatermark(); +} + +TEST_F(SipRouterTest, ResponseDecoder) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + + initializeMetadata(MsgType::Response, MethodType::Ok200); + auto& transaction_info_ptr = (*transaction_infos_)[cluster_name_]; + EXPECT_NE(nullptr, transaction_info_ptr); + std::shared_ptr upstream_request_ptr = + transaction_info_ptr->getUpstreamRequest("10.0.0.1"); + EXPECT_NE(nullptr, upstream_request_ptr); + std::shared_ptr response_decoder_ptr = + std::make_shared(*upstream_request_ptr); + EXPECT_EQ(FilterStatus::Continue, response_decoder_ptr->transportBegin(metadata_)); + EXPECT_EQ(FilterStatus::Continue, response_decoder_ptr->messageBegin(metadata_)); + EXPECT_EQ(FilterStatus::Continue, response_decoder_ptr->messageEnd()); + EXPECT_EQ(FilterStatus::Continue, response_decoder_ptr->transportEnd()); + response_decoder_ptr->newDecoderEventHandler(metadata_); + + // No active trans + metadata_->setTransactionId(nullptr); + EXPECT_EQ(FilterStatus::Continue, response_decoder_ptr->transportBegin(metadata_)); + // No transid + metadata_->resetTransactionId(); + EXPECT_EQ(FilterStatus::StopIteration, response_decoder_ptr->transportBegin(metadata_)); +} + +TEST_F(SipRouterTest, TransactionInfoItem) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + + initializeMetadata(MsgType::Request); + auto& transaction_info_ptr = (*transaction_infos_)[cluster_name_]; + EXPECT_NE(nullptr, transaction_info_ptr); + std::shared_ptr upstream_request_ptr = + transaction_info_ptr->getUpstreamRequest("10.0.0.1"); + EXPECT_NE(nullptr, upstream_request_ptr); + + std::shared_ptr item = + std::make_shared(&callbacks_, upstream_request_ptr); + item->appendMessageList(metadata_); + item->resetTrans(); + EXPECT_NE(nullptr, item->upstreamRequest()); + EXPECT_EQ(false, item->deleted()); +} + +TEST_F(SipRouterTest, Audit) { + initializeTrans(); + initializeRouter(); + initializeTransaction(); + startRequest(MsgType::Request); + + initializeMetadata(MsgType::Request); + auto& transaction_info_ptr = (*transaction_infos_)[cluster_name_]; + EXPECT_NE(nullptr, transaction_info_ptr); + std::shared_ptr upstream_request_ptr = + transaction_info_ptr->getUpstreamRequest("10.0.0.1"); + EXPECT_NE(nullptr, upstream_request_ptr); + + std::shared_ptr item = + std::make_shared(&callbacks_, upstream_request_ptr); + std::shared_ptr itemToDelete = + std::make_shared(&callbacks_, upstream_request_ptr); + itemToDelete->toDelete(); + ThreadLocalTransactionInfo threadInfo(transaction_info_ptr, dispatcher_, + std::chrono::milliseconds(0), "", "x-suri"); + threadInfo.transaction_info_map_.emplace(cluster_name_, item); + threadInfo.transaction_info_map_.emplace("test1", itemToDelete); + threadInfo.auditTimerAction(); +} + +} // namespace Router +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/sip_proxy/filters/network/test/utility.h b/contrib/sip_proxy/filters/network/test/utility.h new file mode 100644 index 0000000000000..c774d5d2e823c --- /dev/null +++ b/contrib/sip_proxy/filters/network/test/utility.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/byte_order.h" + +#include "test/common/buffer/utility.h" + +#include "absl/strings/ascii.h" +#include "contrib/envoy/extensions/filters/network/sip_proxy/v3alpha/sip_proxy.pb.h" +#include "contrib/sip_proxy/filters/network/source/sip.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SipProxy { +namespace { + +using Envoy::Buffer::addRepeated; // NOLINT(misc-unused-using-decls) +using Envoy::Buffer::addSeq; // NOLINT(misc-unused-using-decls) + +MATCHER_P2(HasAppException, t, m, "") { + if (!arg.hasAppException()) { + *result_listener << "has no exception"; + return false; + } + + if (arg.appExceptionType() != t) { + *result_listener << "has exception with type " << static_cast(arg.appExceptionType()); + return false; + } + + if (std::string(m) != arg.appExceptionMessage()) { + *result_listener << "has exception with message " << arg.appExceptionMessage(); + return false; + } + + return true; +} + +} // namespace +} // namespace SipProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/docs/root/api-v3/config/contrib/contrib.rst b/docs/root/api-v3/config/contrib/contrib.rst index c7c91e0754a3e..42596047a775f 100644 --- a/docs/root/api-v3/config/contrib/contrib.rst +++ b/docs/root/api-v3/config/contrib/contrib.rst @@ -1,5 +1,11 @@ +.. _api-v3_config_contrib: + +Contrib Extensions +================== + .. toctree:: :glob: :maxdepth: 2 - ../../extensions/private_key_providers/cryptomb/v3alpha/* + sip/sip + cryptomb/cryptomb diff --git a/docs/root/api-v3/config/contrib/cryptomb/cryptomb.rst b/docs/root/api-v3/config/contrib/cryptomb/cryptomb.rst new file mode 100644 index 0000000000000..9f56bc9c0b8ac --- /dev/null +++ b/docs/root/api-v3/config/contrib/cryptomb/cryptomb.rst @@ -0,0 +1,5 @@ +.. toctree:: + :glob: + :maxdepth: 2 + + ../../../extensions/private_key_providers/cryptomb/v3alpha/* diff --git a/docs/root/api-v3/config/contrib/sip/sip.rst b/docs/root/api-v3/config/contrib/sip/sip.rst new file mode 100644 index 0000000000000..bfc7d61c6cf43 --- /dev/null +++ b/docs/root/api-v3/config/contrib/sip/sip.rst @@ -0,0 +1,6 @@ +.. toctree:: + :glob: + :maxdepth: 2 + + ../../../extensions/filters/network/sip_proxy/router/v3alpha/* + ../../../extensions/filters/network/sip_proxy/v3alpha/* diff --git a/tools/extensions/extensions_check.py b/tools/extensions/extensions_check.py index 4be78f4740c24..b4d35c6b218cb 100644 --- a/tools/extensions/extensions_check.py +++ b/tools/extensions/extensions_check.py @@ -52,7 +52,7 @@ "envoy.matching.input_matchers", "envoy.tls.key_providers", "envoy.quic.proof_source", "envoy.quic.server.crypto_stream", "envoy.rate_limit_descriptors", "envoy.request_id", "envoy.resource_monitors", "envoy.retry_host_predicates", "envoy.retry_priorities", - "envoy.stats_sinks", "envoy.thrift_proxy.filters", "envoy.tracers", + "envoy.stats_sinks", "envoy.thrift_proxy.filters", "envoy.tracers", "envoy.sip_proxy.filters", "envoy.transport_sockets.downstream", "envoy.transport_sockets.upstream", "envoy.tls.cert_validator", "envoy.upstreams", "envoy.wasm.runtime", "envoy.common.key_value") diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 434f6e8b4b685..f9d05beace12e 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1293,3 +1293,10 @@ zlib OBQ SemVer SCM +SCTP +CRLF +clen +crlf +ep +suri +transid