Skip to content
Draft

test #41513

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/dns_filter/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ licenses(["notice"]) # Apache 2
api_proto_package(
deps = [
"//envoy/annotations:pkg",
"//envoy/config/accesslog/v3:pkg",
"//envoy/config/core/v3:pkg",
"//envoy/data/dns/v3:pkg",
"@com_github_cncf_xds//udpa/annotations:pkg",
Expand Down
12 changes: 12 additions & 0 deletions api/envoy/extensions/filters/udp/dns_filter/v3/dns_filter.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";

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

import "envoy/config/accesslog/v3/accesslog.proto";
import "envoy/config/core/v3/address.proto";
import "envoy/config/core/v3/base.proto";
import "envoy/config/core/v3/extension.proto";
Expand Down Expand Up @@ -111,4 +112,15 @@ message DnsFilterConfig {
// 2. Otherwise, uses the default c-ares DNS resolver.
//
ClientContextConfig client_config = 3;

// Configuration for :ref:`access logs <arch_overview_access_logs>`
// emitted by the DNS filter for each DNS query received.
// Supports custom format commands for DNS-specific attributes:
// - ``QUERY_NAME``: The DNS query name being resolved
// - ``QUERY_TYPE``: The DNS query type (A, AAAA, SRV, etc.)
// - ``QUERY_CLASS``: The DNS query class
// - ``ANSWER_COUNT``: Number of answers in the response
// - ``RESPONSE_CODE``: DNS response code
// - ``PARSE_STATUS``: Whether the query was successfully parsed
repeated config.accesslog.v3.AccessLog access_log = 4;
}
335 changes: 335 additions & 0 deletions changelogs/current.yaml

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions source/extensions/filters/udp/dns_filter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ envoy_cc_library(
name = "dns_filter_lib",
srcs = [
"dns_filter.cc",
"dns_filter_access_log.cc",
"dns_filter_resolver.cc",
"dns_filter_utils.cc",
"dns_parser.cc",
],
hdrs = [
"dns_filter.h",
"dns_filter_access_log.h",
"dns_filter_constants.h",
"dns_filter_resolver.h",
"dns_filter_utils.h",
Expand All @@ -28,6 +30,7 @@ envoy_cc_library(
"//bazel/foreign_cc:ares",
"//envoy/buffer:buffer_interface",
"//envoy/event:dispatcher_interface",
"//envoy/formatter:substitution_formatter_interface",
"//envoy/network:address_interface",
"//envoy/network:dns_interface",
"//envoy/network:filter_interface",
Expand All @@ -38,10 +41,12 @@ envoy_cc_library(
"//source/common/common:safe_memcpy_lib",
"//source/common/config:config_provider_lib",
"//source/common/config:datasource_lib",
"//source/common/formatter:substitution_format_string_lib",
"//source/common/network:address_lib",
"//source/common/network:utility_lib",
"//source/common/network/dns_resolver:dns_factory_util_lib",
"//source/common/protobuf:message_validator_lib",
"//source/common/protobuf:utility_lib",
"//source/common/runtime:runtime_lib",
"//source/common/upstream:cluster_manager_lib",
"@envoy_api//envoy/extensions/filters/udp/dns_filter/v3:pkg_cc_proto",
Expand Down
40 changes: 40 additions & 0 deletions source/extensions/filters/udp/dns_filter/dns_filter.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#include "source/extensions/filters/udp/dns_filter/dns_filter.h"

#include "envoy/network/listener.h"
#include "envoy/registry/registry.h"
#include "envoy/type/matcher/v3/string.pb.h"

#include "source/common/config/datasource.h"
#include "source/common/config/utility.h"
#include "source/common/network/address_impl.h"
#include "source/common/network/dns_resolver/dns_factory_util.h"
#include "source/common/protobuf/message_validator_impl.h"
#include "source/common/protobuf/utility.h"
#include "source/extensions/filters/udp/dns_filter/dns_filter_access_log.h"
#include "source/extensions/filters/udp/dns_filter/dns_filter_utils.h"

namespace Envoy {
Expand Down Expand Up @@ -193,6 +197,15 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig(
}
max_pending_lookups_ = 0;
}

// Initialize access logs with DNS-specific command parser
for (const auto& log_config : config.access_log()) {
std::vector<Formatter::CommandParserPtr> command_parsers;
command_parsers.push_back(createDnsFilterCommandParser());
AccessLog::InstanceSharedPtr current_access_log =
AccessLog::AccessLogFactory::fromProto(log_config, context, std::move(command_parsers));
access_logs_.push_back(current_access_log);
}
}

void DnsFilterEnvoyConfig::addEndpointToSuffix(const absl::string_view suffix,
Expand Down Expand Up @@ -317,6 +330,10 @@ void DnsFilter::sendDnsResponse(DnsQueryContextPtr query_context) {
message_parser_.buildResponseBuffer(query_context, response);
config_->stats().downstream_tx_responses_.inc();
config_->stats().downstream_tx_bytes_.recordValue(response.length());

// Log the DNS query
logQuery(query_context);

Network::UdpSendData response_data{query_context->local_->ip(), *(query_context->peer_),
response};
listener_.send(response_data);
Expand Down Expand Up @@ -629,6 +646,29 @@ Network::FilterStatus DnsFilter::onReceiveError(Api::IoError::IoErrorCode error_
return Network::FilterStatus::StopIteration;
}

void DnsFilter::logQuery(const DnsQueryContextPtr& context) {
if (config_->accessLogs().empty()) {
return;
}

// Create connection info provider with local and remote addresses
auto connection_info =
std::make_shared<Network::ConnectionInfoSetterImpl>(context->local_, context->peer_);

// Create a StreamInfo for access logging
StreamInfo::StreamInfoImpl stream_info(listener_.dispatcher().timeSource(), connection_info,
StreamInfo::FilterState::LifeSpan::Connection);

// Create formatter context with DNS query context extension
Formatter::Context formatter_context;
formatter_context.setExtension(*context);

// Log to all configured access loggers
for (const auto& access_log : config_->accessLogs()) {
access_log->log(formatter_context, stream_info);
}
}

} // namespace DnsFilter
} // namespace UdpFilters
} // namespace Extensions
Expand Down
15 changes: 15 additions & 0 deletions source/extensions/filters/udp/dns_filter/dns_filter.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#pragma once

#include "envoy/access_log/access_log.h"
#include "envoy/event/file_event.h"
#include "envoy/extensions/filters/udp/dns_filter/v3/dns_filter.pb.h"
#include "envoy/network/dns.h"
#include "envoy/network/filter.h"

#include "source/common/access_log/access_log_impl.h"
#include "source/common/buffer/buffer_impl.h"
#include "source/common/common/radix_tree.h"
#include "source/common/config/config_provider_impl.h"
#include "source/common/network/socket_impl.h"
#include "source/common/network/utility.h"
#include "source/common/stream_info/stream_info_impl.h"
#include "source/extensions/filters/udp/dns_filter/dns_filter_resolver.h"
#include "source/extensions/filters/udp/dns_filter/dns_parser.h"

Expand All @@ -19,6 +23,8 @@ namespace Extensions {
namespace UdpFilters {
namespace DnsFilter {

inline constexpr absl::string_view DnsFilterName = "envoy.filters.udp.dns_filter";

/**
* All DNS Filter stats. @see stats_macros.h
*/
Expand Down Expand Up @@ -98,6 +104,7 @@ class DnsFilterEnvoyConfig : public Logger::Loggable<Logger::Id::filter> {
const Network::DnsResolverFactory& dnsResolverFactory() const { return *dns_resolver_factory_; }
Api::Api& api() const { return api_; }
const RadixTree<DnsVirtualDomainConfigSharedPtr>& getDnsTrie() const { return dns_lookup_trie_; }
const AccessLog::InstanceSharedPtrVector& accessLogs() const { return access_logs_; }

private:
static DnsFilterStats generateStats(const std::string& stat_prefix, Stats::Scope& scope) {
Expand Down Expand Up @@ -130,6 +137,7 @@ class DnsFilterEnvoyConfig : public Logger::Loggable<Logger::Id::filter> {
uint64_t max_pending_lookups_;
envoy::config::core::v3::TypedExtensionConfig typed_dns_resolver_config_;
Network::DnsResolverFactory* dns_resolver_factory_;
AccessLog::InstanceSharedPtrVector access_logs_;
};

using DnsFilterEnvoyConfigSharedPtr = std::shared_ptr<const DnsFilterEnvoyConfig>;
Expand Down Expand Up @@ -369,6 +377,13 @@ class DnsFilter : public Network::UdpListenerReadFilter, Logger::Loggable<Logger
*/
const absl::string_view getClusterNameForDomain(const absl::string_view domain);

/**
* @brief Logs the DNS query to configured access loggers
*
* @param context object containing the query context
*/
void logQuery(const DnsQueryContextPtr& context);

const DnsFilterEnvoyConfigSharedPtr config_;
Network::UdpListener& listener_;
Upstream::ClusterManager& cluster_manager_;
Expand Down
146 changes: 146 additions & 0 deletions source/extensions/filters/udp/dns_filter/dns_filter_access_log.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "source/extensions/filters/udp/dns_filter/dns_filter_access_log.h"

#include "source/common/formatter/substitution_format_string.h"
#include "source/common/protobuf/utility.h"
#include "source/extensions/filters/udp/dns_filter/dns_parser.h"

#include "absl/container/flat_hash_map.h"
#include "absl/strings/str_cat.h"

namespace Envoy {
namespace Extensions {
namespace UdpFilters {
namespace DnsFilter {

namespace {

/**
* FormatterProvider for DNS-specific fields from DnsQueryContext.
*/
class DnsFormatterProvider : public Formatter::FormatterProvider {
public:
using FieldExtractor = std::function<absl::optional<std::string>(const Formatter::Context&,
const StreamInfo::StreamInfo&)>;

DnsFormatterProvider(FieldExtractor field_extractor)
: field_extractor_(std::move(field_extractor)) {}

// FormatterProvider
absl::optional<std::string>
formatWithContext(const Formatter::Context& context,
const StreamInfo::StreamInfo& stream_info) const override {
return field_extractor_(context, stream_info);
}

Protobuf::Value formatValueWithContext(const Formatter::Context& context,
const StreamInfo::StreamInfo& stream_info) const override {
const auto str = field_extractor_(context, stream_info);
return str.has_value() ? ValueUtil::stringValue(str.value()) : ValueUtil::nullValue();
}

private:
const FieldExtractor field_extractor_;
};

/**
* Helper to create formatter provider for query-dependent fields (fields that require queries_[0]).
*/
template <typename FieldAccessor>
Formatter::FormatterProviderPtr makeQueryFieldProvider(FieldAccessor accessor) {
return std::make_unique<DnsFormatterProvider>(
[accessor](const Formatter::Context& ctx,
const StreamInfo::StreamInfo&) -> absl::optional<std::string> {
const auto dns_ctx = ctx.typedExtension<DnsQueryContext>();
if (!dns_ctx.has_value() || dns_ctx->queries_.empty()) {
return absl::nullopt;
}
return absl::StrCat(accessor(*dns_ctx));
});
}

/**
* Helper to create formatter provider for context-level fields (fields that don't require queries).
*/
template <typename FieldAccessor>
Formatter::FormatterProviderPtr makeContextFieldProvider(FieldAccessor accessor) {
return std::make_unique<DnsFormatterProvider>(
[accessor](const Formatter::Context& ctx,
const StreamInfo::StreamInfo&) -> absl::optional<std::string> {
const auto dns_ctx = ctx.typedExtension<DnsQueryContext>();
if (!dns_ctx.has_value()) {
return absl::nullopt;
}
return accessor(*dns_ctx);
});
}

/**
* DNS Filter command parser implementation.
*/
class DnsFilterCommandParser : public Formatter::CommandParser {
public:
using ProviderFunc =
std::function<Formatter::FormatterProviderPtr(absl::string_view, absl::optional<size_t>)>;
using ProviderFuncTable = absl::flat_hash_map<std::string, ProviderFunc>;

// CommandParser
Formatter::FormatterProviderPtr parse(absl::string_view command, absl::string_view command_arg,
absl::optional<size_t> max_length) const override {
const auto& provider_table = providerFuncTable();
const auto func_it = provider_table.find(std::string(command));
if (func_it == provider_table.end()) {
return nullptr;
}
return func_it->second(command_arg, max_length);
}

private:
static const ProviderFuncTable& providerFuncTable() {
CONSTRUCT_ON_FIRST_USE(
ProviderFuncTable,
{
{"QUERY_NAME",
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
return makeQueryFieldProvider(
[](const DnsQueryContext& ctx) { return ctx.queries_[0]->name_; });
}},
{"QUERY_TYPE",
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
return makeQueryFieldProvider(
[](const DnsQueryContext& ctx) { return ctx.queries_[0]->type_; });
}},
{"QUERY_CLASS",
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
return makeQueryFieldProvider(
[](const DnsQueryContext& ctx) { return ctx.queries_[0]->class_; });
}},
{"ANSWER_COUNT",
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
return makeContextFieldProvider(
[](const DnsQueryContext& ctx) { return absl::StrCat(ctx.answers_.size()); });
}},
{"RESPONSE_CODE",
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
return makeContextFieldProvider(
[](const DnsQueryContext& ctx) { return absl::StrCat(ctx.response_code_); });
}},
{"PARSE_STATUS",
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
return makeContextFieldProvider([](const DnsQueryContext& ctx) -> std::string {
return ctx.parse_status_ ? "true" : "false";
});
}},
});
}
};

} // namespace

Formatter::CommandParserPtr createDnsFilterCommandParser() {
return std::make_unique<DnsFilterCommandParser>();
}

} // namespace DnsFilter
} // namespace UdpFilters
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include "envoy/formatter/substitution_formatter_base.h"

namespace Envoy {
namespace Extensions {
namespace UdpFilters {
namespace DnsFilter {

/**
* Creates a DNS filter-specific command parser for access logging.
* Supports custom format commands for DNS-specific attributes:
* - QUERY_NAME: The DNS query name being resolved
* - QUERY_TYPE: The DNS query type (A, AAAA, SRV, etc.)
* - QUERY_CLASS: The DNS query class
* - ANSWER_COUNT: Number of answers in the response
* - RESPONSE_CODE: DNS response code
* - PARSE_STATUS: Whether the query was successfully parsed
*
* @return CommandParserPtr DNS filter command parser
*/
Formatter::CommandParserPtr createDnsFilterCommandParser();

} // namespace DnsFilter
} // namespace UdpFilters
} // namespace Extensions
} // namespace Envoy
3 changes: 2 additions & 1 deletion source/extensions/filters/udp/dns_filter/dns_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "envoy/buffer/buffer.h"
#include "envoy/common/platform.h"
#include "envoy/common/random_generator.h"
#include "envoy/formatter/http_formatter_context.h"
#include "envoy/network/address.h"
#include "envoy/network/dns.h"
#include "envoy/network/listener.h"
Expand Down Expand Up @@ -184,7 +185,7 @@ PACKED_STRUCT(struct DnsHeader {
/**
* DnsQueryContext contains all the data necessary for responding to a query from a given client.
*/
class DnsQueryContext {
class DnsQueryContext : public Formatter::Context::Extension {
public:
DnsQueryContext(Network::Address::InstanceConstSharedPtr local,
Network::Address::InstanceConstSharedPtr peer, DnsParserCounters& counters,
Expand Down
Loading