Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy Protocol: Pass through V2 TLVs to upstream #24944

Merged
merged 11 commits into from
Feb 2, 2023
24 changes: 24 additions & 0 deletions api/envoy/config/core/v3/proxy_protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package envoy.config.core.v3;

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

option java_package = "io.envoyproxy.envoy.config.core.v3";
option java_outer_classname = "ProxyProtocolProto";
Expand All @@ -12,6 +13,25 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Proxy protocol]

message ProxyProtocolPassThroughTLVs {
enum PassTLVsMatchType {
// Pass all TLVs.
INCLUDE_ALL = 0;

// Pass specific TLVs defined in tlv_types
INCLUDE = 1;
}

// The strategy to pass through TLVs. The default is INCLUDE_ALL.
// If INCLUDE_ALL is set, all TLVs will be passed through no matter the tlv_types field.
PassTLVsMatchType match_type = 1;

// The TLV types that are applied based on the match type.
// TLV type is defined as uint8_t in proxy protocol. See `the spec
// <https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt>`_ for details.
repeated uint32 tlv_type = 2 [(validate.rules).repeated = {items {uint32 {lt: 256}}}];
}

message ProxyProtocolConfig {
enum Version {
// PROXY protocol version 1. Human readable format.
Expand All @@ -23,4 +43,8 @@ message ProxyProtocolConfig {

// The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details
Version version = 1;

// This config controls which TLVs can be passed to filter state if it is Proxy Protocol
// V2 header. If there is no setting for this field, no TLVs will be passed through.
ProxyProtocolPassThroughTLVs pass_through_tlvs = 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ 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"],
deps = [
"//envoy/config/core/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ syntax = "proto3";

package envoy.extensions.filters.listener.proxy_protocol.v3;

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

import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";
Expand Down Expand Up @@ -58,4 +60,8 @@ message ProxyProtocol {
// signature will timeout (Envoy is unable to differentiate these requests
// from incomplete proxy protocol requests).
bool allow_requests_without_proxy_protocol = 2;

// This config controls which TLVs can be passed to filter state if it is Proxy Protocol
// V2 header. If there is no setting for this field, no TLVs will be passed through.
config.core.v3.ProxyProtocolPassThroughTLVs pass_through_tlvs = 3;
}
12 changes: 12 additions & 0 deletions envoy/network/proxy_protocol.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
#pragma once

#include <cstdint>
#include <string>
#include <vector>

#include "envoy/network/address.h"

namespace Envoy {
namespace Network {

struct ProxyProtocolTLV {
const uint8_t type;
const std::string value;
};

using ProxyProtocolTLVVector = std::vector<ProxyProtocolTLV>;

struct ProxyProtocolData {
const Network::Address::InstanceConstSharedPtr src_addr_;
const Network::Address::InstanceConstSharedPtr dst_addr_;
const ProxyProtocolTLVVector tlv_vector_{};
std::string asStringForHash() const {
return std::string(src_addr_ ? src_addr_->asString() : "null") +
(dst_addr_ ? dst_addr_->asString() : "null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ void generateV1Header(const Network::Address::Ip& source_address,

void generateV2Header(const std::string& src_addr, const std::string& dst_addr, uint32_t src_port,
uint32_t dst_port, Network::Address::IpVersion ip_version,
Buffer::Instance& out) {
uint16_t extension_length, Buffer::Instance& out) {
out.add(PROXY_PROTO_V2_SIGNATURE, PROXY_PROTO_V2_SIGNATURE_LEN);

const uint8_t version_and_command = PROXY_PROTO_V2_VERSION << 4 | PROXY_PROTO_V2_ONBEHALF_OF;
Expand All @@ -61,11 +61,15 @@ void generateV2Header(const std::string& src_addr, const std::string& dst_addr,
address_family_and_protocol |= PROXY_PROTO_V2_TRANSPORT_STREAM;
out.add(&address_family_and_protocol, 1);

uint8_t addr_length[2]{0, 0};
// Number of following bytes part of the header in V2 protocol.
uint16_t addr_length;
uint16_t addr_length_n; // Network byte order

switch (ip_version) {
case Network::Address::IpVersion::v4: {
addr_length[1] = PROXY_PROTO_V2_ADDR_LEN_INET;
out.add(addr_length, 2);
addr_length = PROXY_PROTO_V2_ADDR_LEN_INET + extension_length;
addr_length_n = htons(addr_length);
out.add(&addr_length_n, 2);
const uint32_t net_src_addr =
Network::Address::Ipv4Instance(src_addr, src_port).ip()->ipv4()->address();
const uint32_t net_dst_addr =
Expand All @@ -75,8 +79,9 @@ void generateV2Header(const std::string& src_addr, const std::string& dst_addr,
break;
}
case Network::Address::IpVersion::v6: {
addr_length[1] = PROXY_PROTO_V2_ADDR_LEN_INET6;
out.add(addr_length, 2);
addr_length = PROXY_PROTO_V2_ADDR_LEN_INET6 + extension_length;
addr_length_n = htons(addr_length);
out.add(&addr_length_n, 2);
const absl::uint128 net_src_addr =
Network::Address::Ipv6Instance(src_addr, src_port).ip()->ipv6()->address();
const absl::uint128 net_dst_addr =
Expand All @@ -93,10 +98,48 @@ void generateV2Header(const std::string& src_addr, const std::string& dst_addr,
out.add(&net_dst_port, 2);
}

void generateV2Header(const std::string& src_addr, const std::string& dst_addr, uint32_t src_port,
uint32_t dst_port, Network::Address::IpVersion ip_version,
Buffer::Instance& out) {
generateV2Header(src_addr, dst_addr, src_port, dst_port, ip_version, 0, out);
}

void generateV2Header(const Network::Address::Ip& source_address,
const Network::Address::Ip& dest_address, Buffer::Instance& out) {
generateV2Header(source_address.addressAsString(), dest_address.addressAsString(),
source_address.port(), dest_address.port(), source_address.version(), out);
source_address.port(), dest_address.port(), source_address.version(), 0, out);
}

void generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer::Instance& out,
bool pass_all_tlvs, const absl::flat_hash_set<uint8_t>& pass_through_tlvs) {
uint64_t extension_length = 0;
for (auto&& tlv : proxy_proto_data.tlv_vector_) {
if (!pass_all_tlvs && !pass_through_tlvs.contains(tlv.type)) {
continue;
}
extension_length += PROXY_PROTO_V2_TLV_TYPE_LENGTH_LEN + tlv.value.size();
if (extension_length > std::numeric_limits<uint16_t>::max()) {
ExceptionUtil::throwEnvoyException(
fmt::format("proxy protocol TLVs exceed length limit {}, already got {}",
std::numeric_limits<uint16_t>::max(), extension_length));
}
}
assert(extension_length <= std::numeric_limits<uint16_t>::max());
const auto& src = *proxy_proto_data.src_addr_->ip();
const auto& dst = *proxy_proto_data.dst_addr_->ip();
generateV2Header(src.addressAsString(), dst.addressAsString(), src.port(), dst.port(),
src.version(), static_cast<uint16_t>(extension_length), out);

// Generate the TLV vector.
for (auto&& tlv : proxy_proto_data.tlv_vector_) {
if (!pass_all_tlvs && !pass_through_tlvs.contains(tlv.type)) {
continue;
}
out.add(&tlv.type, 1);
uint16_t size = htons(static_cast<uint16_t>(tlv.value.size()));
out.add(&size, sizeof(uint16_t));
out.add(tlv.value.c_str(), tlv.value.size());
}
}

void generateProxyProtoHeader(const envoy::config::core::v3::ProxyProtocolConfig& config,
Expand Down
11 changes: 11 additions & 0 deletions source/extensions/common/proxy_protocol/proxy_protocol_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "envoy/network/address.h"
#include "envoy/network/connection.h"

#include "absl/container/flat_hash_set.h"

namespace Envoy {
namespace Extensions {
namespace Common {
Expand Down Expand Up @@ -39,6 +41,8 @@ constexpr uint32_t PROXY_PROTO_V2_ADDR_LEN_INET = 12;
constexpr uint32_t PROXY_PROTO_V2_ADDR_LEN_INET6 = 36;
constexpr uint32_t PROXY_PROTO_V2_ADDR_LEN_UNIX = 216;

constexpr uint32_t PROXY_PROTO_V2_TLV_TYPE_LENGTH_LEN = 3;

// Generates the v1 PROXY protocol header and adds it to the specified buffer
void generateV1Header(const std::string& src_addr, const std::string& dst_addr, uint32_t src_port,
uint32_t dst_port, Network::Address::IpVersion ip_version,
Expand All @@ -48,6 +52,9 @@ void generateV1Header(const Network::Address::Ip& source_address,

// Generates the v2 PROXY protocol header and adds it to the specified buffer
// TCP is assumed as the transport protocol
void generateV2Header(const std::string& src_addr, const std::string& dst_addr, uint32_t src_port,
uint32_t dst_port, Network::Address::IpVersion ip_version,
uint16_t extension_length, Buffer::Instance& out);
void generateV2Header(const std::string& src_addr, const std::string& dst_addr, uint32_t src_port,
uint32_t dst_port, Network::Address::IpVersion ip_version,
Buffer::Instance& out);
Expand All @@ -61,6 +68,10 @@ void generateProxyProtoHeader(const envoy::config::core::v3::ProxyProtocolConfig
// Generates the v2 PROXY protocol local command header and adds it to the specified buffer
void generateV2LocalHeader(Buffer::Instance& out);

// Generates the v2 PROXY protocol header including the TLV vector into the specified buffer.
void generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer::Instance& out,
bool pass_all_tlvs, const absl::flat_hash_set<uint8_t>& pass_through_tlvs);

} // namespace ProxyProtocol
} // namespace Common
} // namespace Extensions
Expand Down
3 changes: 3 additions & 0 deletions source/extensions/filters/listener/proxy_protocol/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ envoy_cc_library(
"//source/common/buffer:buffer_lib",
"//source/common/common:assert_lib",
"//source/common/common:empty_string",
"//source/common/common:hex_lib",
"//source/common/common:minimal_logger_lib",
"//source/common/common:safe_memcpy_lib",
"//source/common/common:utility_lib",
"//source/common/network:address_lib",
"//source/common/network:proxy_protocol_filter_state_lib",
"//source/common/network:utility_lib",
"//source/extensions/common/proxy_protocol:proxy_protocol_header_lib",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@envoy_api//envoy/extensions/filters/listener/proxy_protocol/v3:pkg_cc_proto",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "envoy/common/exception.h"
#include "envoy/common/platform.h"
#include "envoy/config/core/v3/proxy_protocol.pb.h"
#include "envoy/event/dispatcher.h"
#include "envoy/network/listen_socket.h"
#include "envoy/stats/scope.h"
Expand All @@ -17,12 +18,15 @@
#include "source/common/common/assert.h"
#include "source/common/common/empty_string.h"
#include "source/common/common/fmt.h"
#include "source/common/common/hex.h"
#include "source/common/common/safe_memcpy.h"
#include "source/common/common/utility.h"
#include "source/common/network/address_impl.h"
#include "source/common/network/proxy_protocol_filter_state.h"
#include "source/common/network/utility.h"
#include "source/extensions/common/proxy_protocol/proxy_protocol_header.h"

using envoy::config::core::v3::ProxyProtocolPassThroughTLVs;
using Envoy::Extensions::Common::ProxyProtocol::PROXY_PROTO_V1_SIGNATURE;
using Envoy::Extensions::Common::ProxyProtocol::PROXY_PROTO_V1_SIGNATURE_LEN;
using Envoy::Extensions::Common::ProxyProtocol::PROXY_PROTO_V2_ADDR_LEN_INET;
Expand Down Expand Up @@ -51,6 +55,19 @@ Config::Config(
for (const auto& rule : proto_config.rules()) {
tlv_types_[0xFF & rule.tlv_type()] = rule.on_tlv_present();
}

if (proto_config.has_pass_through_tlvs()) {
if (proto_config.pass_through_tlvs().match_type() ==
ProxyProtocolPassThroughTLVs::INCLUDE_ALL) {
pass_all_tlvs_ = true;
} else if (proto_config.pass_through_tlvs().match_type() ==
ProxyProtocolPassThroughTLVs::INCLUDE) {
pass_all_tlvs_ = false;
for (const auto& tlv_type : proto_config.pass_through_tlvs().tlv_type()) {
pass_through_tlvs_.insert(0xFF & tlv_type);
}
}
}
}

const KeyValuePair* Config::isTlvTypeNeeded(uint8_t type) const {
Expand All @@ -62,6 +79,13 @@ const KeyValuePair* Config::isTlvTypeNeeded(uint8_t type) const {
return nullptr;
}

bool Config::isPassThroughTlvTypeNeeded(uint8_t tlv_type) const {
if (pass_all_tlvs_) {
return true;
}
return pass_through_tlvs_.contains(tlv_type);
}

size_t Config::numberOfNeededTlvTypes() const { return tlv_types_.size(); }

bool Config::allowRequestsWithoutProxyProtocol() const {
Expand Down Expand Up @@ -119,6 +143,29 @@ ReadOrParseState Filter::parseBuffer(Network::ListenerFilterBuffer& buffer) {
}
}

if (proxy_protocol_header_.has_value() &&
!cb_->filterState().hasData<Network::ProxyProtocolFilterState>(
Network::ProxyProtocolFilterState::key())) {
if (!proxy_protocol_header_.value().local_command_) {
auto buf = reinterpret_cast<const uint8_t*>(buffer.rawSlice().mem_);
ENVOY_LOG(
trace,
"Parsed proxy protocol header, length: {}, buffer: {}, TLV length: {}, TLV buffer: {}",
proxy_protocol_header_.value().wholeHeaderLength(),
Envoy::Hex::encode(buf, proxy_protocol_header_.value().wholeHeaderLength()),
proxy_protocol_header_.value().extensions_length_,
Envoy::Hex::encode(buf + proxy_protocol_header_.value().headerLengthWithoutExtension(),
proxy_protocol_header_.value().extensions_length_));
}

cb_->filterState().setData(
Network::ProxyProtocolFilterState::key(),
std::make_unique<Network::ProxyProtocolFilterState>(Network::ProxyProtocolData{
proxy_protocol_header_.value().remote_address_,
proxy_protocol_header_.value().local_address_, parsed_tlvs_}),
StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection);
}

if (proxy_protocol_header_.has_value() && !proxy_protocol_header_.value().local_command_) {
// If this is a local_command, we are not to override address
// Error check the source and destination fields. Most errors are caught by the address
Expand Down Expand Up @@ -360,10 +407,11 @@ bool Filter::parseTlvs(const uint8_t* buf, size_t len) {
}

// Only save to dynamic metadata if this type of TLV is needed.
absl::string_view tlv_value(reinterpret_cast<char const*>(buf + idx), tlv_value_length);
auto key_value_pair = config_->isTlvTypeNeeded(tlv_type);
if (nullptr != key_value_pair) {
ProtobufWkt::Value metadata_value;
metadata_value.set_string_value(reinterpret_cast<char const*>(buf + idx), tlv_value_length);
metadata_value.set_string_value(tlv_value.data(), tlv_value.size());

std::string metadata_key = key_value_pair->metadata_namespace().empty()
? "envoy.filters.listener.proxy_protocol"
Expand All @@ -374,7 +422,15 @@ bool Filter::parseTlvs(const uint8_t* buf, size_t len) {
metadata.mutable_fields()->insert({key_value_pair->key(), metadata_value});
cb_->setDynamicMetadata(metadata_key, metadata);
} else {
ENVOY_LOG(trace, "proxy_protocol: Skip TLV of type {} since it's not needed", tlv_type);
ENVOY_LOG(trace,
"proxy_protocol: Skip TLV of type {} since it's not needed for dynamic metadata",
tlv_type);
}

// Save TLVs to the filter state.
if (config_->isPassThroughTlvTypeNeeded(tlv_type)) {
ENVOY_LOG(trace, "proxy_protocol: Storing parsed TLV of type {} to filter state.", tlv_type);
parsed_tlvs_.push_back({tlv_type, std::string(tlv_value)});
}

idx += tlv_value_length;
Expand All @@ -390,9 +446,9 @@ ReadOrParseState Filter::readExtensions(Network::ListenerFilterBuffer& buffer) {
return ReadOrParseState::TryAgainLater;
}

if (proxy_protocol_header_.value().local_command_ || 0 == config_->numberOfNeededTlvTypes()) {
// Ignores the extensions if this is a local command or there's no TLV needs to be saved
// to metadata. Those will drained from the buffer in the end.
if (proxy_protocol_header_.value().local_command_) {
// Ignores the extensions if this is a local command.
// Those will drained from the buffer in the end.
return ReadOrParseState::Done;
}

Expand Down
Loading