Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/envoy/extensions/filters/udp/udp_proxy/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/annotations:pkg",
"//envoy/config/core/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
Expand Down
48 changes: 48 additions & 0 deletions api/envoy/extensions/filters/udp/udp_proxy/v3/route.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
syntax = "proto3";

package envoy.extensions.filters.udp.udp_proxy.v3;

import "envoy/config/core/v3/address.proto";

import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.udp.udp_proxy.v3";
option java_outer_classname = "RouteProto";
option java_multiple_files = true;
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: UDP proxy route configuration]
// UDP proxy :ref:`configuration overview <config_udp_listener_filters_udp_proxy>`.

message RouteConfiguration {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for this we should go straight to using the new style matchers as there is no reason here not to support sub-linear matching for CIDR ranges, etc. Can you take a look at #17633 and replicate that here? Obviously some matchers won't be supported and we will have to deal with that, but many would be. cc @snowp who can help with questions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have noticed these components, but they focus on HTTP and may be not suitable for UDP. In fact, network filters have their own route matchers like RocketMQ route configuration for RocketMQ proxy and Thrift route configuration for Thrift proxy. UDP proxy is a UDP listener filter which is similar to network filters, so I am not sure if we can use these new style matchers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new matching API was developed in order to avoid each protocol from having to define their own matching APIs - the API itself should not be HTTP specific, only loosely coupled with HTTP through extensions and the context in which they are used. Happy to chat more about how we can make use of the new API in this context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds great! A unified matcher API will bring convenience to development. In UDP, we mainly consider the source prefix and source port.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup as Snow said, the new API was designed for this purpose, so let's please switch to it, even if we only support a small subset of matchers.

/wait

// The list of routes that will be matched, in order, against incoming requests. The first route
// that matches will be used.
repeated Route routes = 1;
}

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 {
// The criteria is satisfied if the source IP address of the downstream
// connection is contained in at least one of the specified subnets. If the
// parameter is not specified or the list is empty, the source IP address is
// ignored.
repeated config.core.v3.CidrRange source_prefix_ranges = 1;
}

message RouteAction {
oneof cluster_specifier {
option (validate.required) = true;

// Indicates the upstream cluster to which the request should be routed.
string cluster = 1;
}
}
13 changes: 11 additions & 2 deletions api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ syntax = "proto3";
package envoy.extensions.filters.udp.udp_proxy.v3;

import "envoy/config/core/v3/udp_socket_config.proto";
import "envoy/extensions/filters/udp/udp_proxy/v3/route.proto";

import "google/protobuf/duration.proto";

import "envoy/annotations/deprecation.proto";
import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";
Expand All @@ -20,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#extension: envoy.filters.udp_listener.udp_proxy]

// Configuration for the UDP proxy filter.
// [#next-free-field: 7]
// [#next-free-field: 8]
message UdpProxyConfig {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.udp.udp_proxy.v2alpha.UdpProxyConfig";
Expand Down Expand Up @@ -50,7 +52,14 @@ message UdpProxyConfig {
option (validate.required) = true;

// The upstream cluster to connect to.
string cluster = 2 [(validate.rules).string = {min_len: 1}];
string cluster = 2 [
deprecated = true,
(validate.rules).string = {min_len: 1},
(envoy.annotations.deprecated_at_minor_version) = "3.0"
];

// The route table for the connection manager is static and is specified in this property.
RouteConfiguration route_config = 7;
}

// The idle timeout for sessions. Idle is defined as no datagrams between received or sent by
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions source/extensions/filters/udp/udp_proxy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ envoy_cc_library(
"//envoy/network:listener_interface",
"//envoy/upstream:cluster_manager_interface",
"//source/common/api:os_sys_calls_lib",
"//source/common/common:empty_string",
"//source/common/network:socket_lib",
"//source/common/network:socket_option_factory_lib",
"//source/common/network:utility_lib",
"//source/common/upstream:load_balancer_lib",
"//source/extensions/filters/udp/udp_proxy/router:router_lib",
"@envoy_api//envoy/extensions/filters/udp/udp_proxy/v3:pkg_cc_proto",
],
)
Expand Down
26 changes: 26 additions & 0 deletions source/extensions/filters/udp/udp_proxy/router/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_library(
name = "router_interface",
hdrs = ["router.h"],
)

envoy_cc_library(
name = "router_lib",
srcs = ["router_impl.cc"],
hdrs = ["router_impl.h"],
deps = [
":router_interface",
"//source/common/network:cidr_range_lib",
"//source/common/network:lc_trie_lib",
"@envoy_api//envoy/extensions/filters/udp/udp_proxy/v3:pkg_cc_proto",
],
)
63 changes: 63 additions & 0 deletions source/extensions/filters/udp/udp_proxy/router/router.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include <memory>
#include <string>
#include <vector>

#include "envoy/common/pure.h"
#include "envoy/network/address.h"

namespace Envoy {
namespace Extensions {
namespace UdpFilters {
namespace UdpProxy {
namespace Router {

/**
* 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;
};

using RouteEntryConstSharedPtr = std::shared_ptr<const RouteEntry>;

/**
* 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<const Route>;

/**
* The router configuration.
*/
class Config {
public:
virtual ~Config() = default;

virtual RouteConstSharedPtr route(Network::Address::InstanceConstSharedPtr address) const PURE;
virtual const std::vector<RouteEntryConstSharedPtr>& entries() const PURE;
};

using ConfigConstSharedPtr = std::shared_ptr<const Config>;

} // namespace Router
} // namespace UdpProxy
} // namespace UdpFilters
} // namespace Extensions
} // namespace Envoy
90 changes: 90 additions & 0 deletions source/extensions/filters/udp/udp_proxy/router/router_impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "source/extensions/filters/udp/udp_proxy/router/router_impl.h"

#include "absl/container/flat_hash_set.h"

namespace Envoy {
namespace Extensions {
namespace UdpFilters {
namespace UdpProxy {
namespace Router {

ClusterRouteEntry::ClusterRouteEntry(
const envoy::extensions::filters::udp::udp_proxy::v3::Route& route)
: cluster_name_(route.route().cluster()) {}

ClusterRouteEntry::ClusterRouteEntry(const std::string& cluster) : cluster_name_(cluster) {}

ConfigImpl::ConfigImpl(const envoy::extensions::filters::udp::udp_proxy::v3::UdpProxyConfig& config)
: cluster_(std::make_shared<ClusterRouteEntry>(config.cluster())),
source_ips_trie_(buildRouteTrie(config.route_config())),
entries_(buildEntryList(config.cluster(), config.route_config())) {}

RouteConstSharedPtr ConfigImpl::route(Network::Address::InstanceConstSharedPtr address) const {
if (!cluster_->routeEntry()->clusterName().empty()) {
return cluster_;
}

const auto& data = source_ips_trie_.getData(address);
if (!data.empty()) {
ASSERT(data.size() == 1);
return data.back();
}

return nullptr;
}

ConfigImpl::SourceIPsTrie ConfigImpl::buildRouteTrie(const RouteConfiguration& config) {
std::vector<std::pair<RouteConstSharedPtr, std::vector<Network::Address::CidrRange>>>
source_ips_list;
source_ips_list.reserve(config.routes().size());

auto convertAddress = [](const auto& prefix_ranges) -> std::vector<Network::Address::CidrRange> {
std::vector<Network::Address::CidrRange> ips;
ips.reserve(prefix_ranges.size());
for (const auto& ip : prefix_ranges) {
const auto& cidr_range = Network::Address::CidrRange::create(ip);
ips.push_back(cidr_range);
}
return ips;
};

for (auto& route : config.routes()) {
auto ranges = route.match().source_prefix_ranges();
auto route_entry = std::make_shared<ClusterRouteEntry>(route);

source_ips_list.push_back(make_pair(route_entry, convertAddress(ranges)));
}

return {source_ips_list, true};
}

std::vector<RouteEntryConstSharedPtr> ConfigImpl::buildEntryList(const std::string& cluster,
const RouteConfiguration& config) {
auto set = absl::flat_hash_set<RouteEntryConstSharedPtr>();

if (!cluster.empty()) {
set.emplace(std::make_shared<ClusterRouteEntry>(cluster));
}

for (const auto& route : config.routes()) {
auto route_entry = std::make_shared<ClusterRouteEntry>(route);
auto cluster_name = route_entry->routeEntry()->clusterName();
if (!cluster_name.empty()) {
set.emplace(std::make_shared<ClusterRouteEntry>(cluster_name));
}
}

auto list = std::vector<RouteEntryConstSharedPtr>();
list.reserve(set.size());
for (const auto& entry : set) {
list.push_back(entry);
}

return list;
}

} // namespace Router
} // namespace UdpProxy
} // namespace UdpFilters
} // namespace Extensions
} // namespace Envoy
Loading