Skip to content

Commit 3ab7bf9

Browse files
AntonKanugwbpcode
andauthored
dns_filter: add access logging support (#41385)
Closes #39671 <!-- !!!ATTENTION!!! If you are fixing *any* crash or *any* potential security issue, *do not* open a pull request in this repo. Please report the issue via emailing [email protected] where the issue will be triaged appropriately. Thank you in advance for helping to keep Envoy secure. !!!ATTENTION!!! For an explanation of how to fill out the fields, please see the relevant section in [PULL_REQUESTS.md](https://github.com/envoyproxy/envoy/blob/main/PULL_REQUESTS.md) --> Commit Message: dns_filter: add access logging support Additional Description: Risk Level: small-medium Testing: unit test Docs Changes: Release Notes: Platform Specific Features: --------- Signed-off-by: Anton Kanugalawattage <[email protected]> Signed-off-by: code <[email protected]> Co-authored-by: code <[email protected]>
1 parent 8c4e6a7 commit 3ab7bf9

File tree

11 files changed

+972
-1
lines changed

11 files changed

+972
-1
lines changed

api/envoy/extensions/filters/udp/dns_filter/v3/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ licenses(["notice"]) # Apache 2
77
api_proto_package(
88
deps = [
99
"//envoy/annotations:pkg",
10+
"//envoy/config/accesslog/v3:pkg",
1011
"//envoy/config/core/v3:pkg",
1112
"//envoy/data/dns/v3:pkg",
1213
"@com_github_cncf_xds//udpa/annotations:pkg",

api/envoy/extensions/filters/udp/dns_filter/v3/dns_filter.proto

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ syntax = "proto3";
22

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

5+
import "envoy/config/accesslog/v3/accesslog.proto";
56
import "envoy/config/core/v3/address.proto";
67
import "envoy/config/core/v3/base.proto";
78
import "envoy/config/core/v3/extension.proto";
@@ -111,4 +112,15 @@ message DnsFilterConfig {
111112
// 2. Otherwise, uses the default c-ares DNS resolver.
112113
//
113114
ClientContextConfig client_config = 3;
115+
116+
// Configuration for :ref:`access logs <arch_overview_access_logs>`
117+
// emitted by the DNS filter for each DNS query received.
118+
// Supports custom format commands for DNS-specific attributes:
119+
// - ``QUERY_NAME``: The DNS query name being resolved
120+
// - ``QUERY_TYPE``: The DNS query type (A, AAAA, SRV, etc.)
121+
// - ``QUERY_CLASS``: The DNS query class
122+
// - ``ANSWER_COUNT``: Number of answers in the response
123+
// - ``RESPONSE_CODE``: DNS response code
124+
// - ``PARSE_STATUS``: Whether the query was successfully parsed
125+
repeated config.accesslog.v3.AccessLog access_log = 4;
114126
}

changelogs/current.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ new_features:
150150
- area: admin
151151
change: |
152152
Added ``/memory/tcmalloc`` admin endpoint that provides TCMalloc memory statistics.
153+
- area: dns_filter
154+
change: |
155+
Added :ref:`access_log <envoy_v3_api_field_extensions.filters.udp.dns_filter.v3.DnsFilterConfig.access_log>` for DNS filter.
153156
- area: quic
154157
change: |
155158
Added QUIC protocol option :ref:`max_sessions_per_event_loop

source/extensions/filters/udp/dns_filter/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ envoy_cc_library(
1313
name = "dns_filter_lib",
1414
srcs = [
1515
"dns_filter.cc",
16+
"dns_filter_access_log.cc",
1617
"dns_filter_resolver.cc",
1718
"dns_filter_utils.cc",
1819
"dns_parser.cc",
1920
],
2021
hdrs = [
2122
"dns_filter.h",
23+
"dns_filter_access_log.h",
2224
"dns_filter_constants.h",
2325
"dns_filter_resolver.h",
2426
"dns_filter_utils.h",
@@ -28,6 +30,7 @@ envoy_cc_library(
2830
"//bazel/foreign_cc:ares",
2931
"//envoy/buffer:buffer_interface",
3032
"//envoy/event:dispatcher_interface",
33+
"//envoy/formatter:substitution_formatter_interface",
3134
"//envoy/network:address_interface",
3235
"//envoy/network:dns_interface",
3336
"//envoy/network:filter_interface",
@@ -38,10 +41,12 @@ envoy_cc_library(
3841
"//source/common/common:safe_memcpy_lib",
3942
"//source/common/config:config_provider_lib",
4043
"//source/common/config:datasource_lib",
44+
"//source/common/formatter:substitution_format_string_lib",
4145
"//source/common/network:address_lib",
4246
"//source/common/network:utility_lib",
4347
"//source/common/network/dns_resolver:dns_factory_util_lib",
4448
"//source/common/protobuf:message_validator_lib",
49+
"//source/common/protobuf:utility_lib",
4550
"//source/common/runtime:runtime_lib",
4651
"//source/common/upstream:cluster_manager_lib",
4752
"@envoy_api//envoy/extensions/filters/udp/dns_filter/v3:pkg_cc_proto",

source/extensions/filters/udp/dns_filter/dns_filter.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
#include "source/extensions/filters/udp/dns_filter/dns_filter.h"
22

33
#include "envoy/network/listener.h"
4+
#include "envoy/registry/registry.h"
45
#include "envoy/type/matcher/v3/string.pb.h"
56

67
#include "source/common/config/datasource.h"
8+
#include "source/common/config/utility.h"
79
#include "source/common/network/address_impl.h"
810
#include "source/common/network/dns_resolver/dns_factory_util.h"
911
#include "source/common/protobuf/message_validator_impl.h"
12+
#include "source/common/protobuf/utility.h"
13+
#include "source/extensions/filters/udp/dns_filter/dns_filter_access_log.h"
1014
#include "source/extensions/filters/udp/dns_filter/dns_filter_utils.h"
1115

1216
namespace Envoy {
@@ -193,6 +197,15 @@ DnsFilterEnvoyConfig::DnsFilterEnvoyConfig(
193197
}
194198
max_pending_lookups_ = 0;
195199
}
200+
201+
// Initialize access logs with DNS-specific command parser
202+
for (const auto& log_config : config.access_log()) {
203+
std::vector<Formatter::CommandParserPtr> command_parsers;
204+
command_parsers.push_back(createDnsFilterCommandParser());
205+
AccessLog::InstanceSharedPtr current_access_log =
206+
AccessLog::AccessLogFactory::fromProto(log_config, context, std::move(command_parsers));
207+
access_logs_.push_back(current_access_log);
208+
}
196209
}
197210

198211
void DnsFilterEnvoyConfig::addEndpointToSuffix(const absl::string_view suffix,
@@ -317,6 +330,10 @@ void DnsFilter::sendDnsResponse(DnsQueryContextPtr query_context) {
317330
message_parser_.buildResponseBuffer(query_context, response);
318331
config_->stats().downstream_tx_responses_.inc();
319332
config_->stats().downstream_tx_bytes_.recordValue(response.length());
333+
334+
// Log the DNS query
335+
logQuery(query_context);
336+
320337
Network::UdpSendData response_data{query_context->local_->ip(), *(query_context->peer_),
321338
response};
322339
listener_.send(response_data);
@@ -629,6 +646,29 @@ Network::FilterStatus DnsFilter::onReceiveError(Api::IoError::IoErrorCode error_
629646
return Network::FilterStatus::StopIteration;
630647
}
631648

649+
void DnsFilter::logQuery(const DnsQueryContextPtr& context) {
650+
if (config_->accessLogs().empty()) {
651+
return;
652+
}
653+
654+
// Create connection info provider with local and remote addresses
655+
auto connection_info =
656+
std::make_shared<Network::ConnectionInfoSetterImpl>(context->local_, context->peer_);
657+
658+
// Create a StreamInfo for access logging
659+
StreamInfo::StreamInfoImpl stream_info(listener_.dispatcher().timeSource(), connection_info,
660+
StreamInfo::FilterState::LifeSpan::Connection);
661+
662+
// Create formatter context with DNS query context extension
663+
Formatter::Context formatter_context;
664+
formatter_context.setExtension(*context);
665+
666+
// Log to all configured access loggers
667+
for (const auto& access_log : config_->accessLogs()) {
668+
access_log->log(formatter_context, stream_info);
669+
}
670+
}
671+
632672
} // namespace DnsFilter
633673
} // namespace UdpFilters
634674
} // namespace Extensions

source/extensions/filters/udp/dns_filter/dns_filter.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
#pragma once
22

3+
#include "envoy/access_log/access_log.h"
34
#include "envoy/event/file_event.h"
45
#include "envoy/extensions/filters/udp/dns_filter/v3/dns_filter.pb.h"
56
#include "envoy/network/dns.h"
67
#include "envoy/network/filter.h"
78

9+
#include "source/common/access_log/access_log_impl.h"
810
#include "source/common/buffer/buffer_impl.h"
911
#include "source/common/common/radix_tree.h"
1012
#include "source/common/config/config_provider_impl.h"
13+
#include "source/common/network/socket_impl.h"
1114
#include "source/common/network/utility.h"
15+
#include "source/common/stream_info/stream_info_impl.h"
1216
#include "source/extensions/filters/udp/dns_filter/dns_filter_resolver.h"
1317
#include "source/extensions/filters/udp/dns_filter/dns_parser.h"
1418

@@ -19,6 +23,8 @@ namespace Extensions {
1923
namespace UdpFilters {
2024
namespace DnsFilter {
2125

26+
inline constexpr absl::string_view DnsFilterName = "envoy.filters.udp.dns_filter";
27+
2228
/**
2329
* All DNS Filter stats. @see stats_macros.h
2430
*/
@@ -98,6 +104,7 @@ class DnsFilterEnvoyConfig : public Logger::Loggable<Logger::Id::filter> {
98104
const Network::DnsResolverFactory& dnsResolverFactory() const { return *dns_resolver_factory_; }
99105
Api::Api& api() const { return api_; }
100106
const RadixTree<DnsVirtualDomainConfigSharedPtr>& getDnsTrie() const { return dns_lookup_trie_; }
107+
const AccessLog::InstanceSharedPtrVector& accessLogs() const { return access_logs_; }
101108

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

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

380+
/**
381+
* @brief Logs the DNS query to configured access loggers
382+
*
383+
* @param context object containing the query context
384+
*/
385+
void logQuery(const DnsQueryContextPtr& context);
386+
372387
const DnsFilterEnvoyConfigSharedPtr config_;
373388
Network::UdpListener& listener_;
374389
Upstream::ClusterManager& cluster_manager_;
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include "source/extensions/filters/udp/dns_filter/dns_filter_access_log.h"
2+
3+
#include "source/common/formatter/substitution_format_string.h"
4+
#include "source/common/protobuf/utility.h"
5+
#include "source/extensions/filters/udp/dns_filter/dns_parser.h"
6+
7+
#include "absl/container/flat_hash_map.h"
8+
#include "absl/strings/str_cat.h"
9+
10+
namespace Envoy {
11+
namespace Extensions {
12+
namespace UdpFilters {
13+
namespace DnsFilter {
14+
15+
namespace {
16+
17+
/**
18+
* FormatterProvider for DNS-specific fields from DnsQueryContext.
19+
*/
20+
class DnsFormatterProvider : public Formatter::FormatterProvider {
21+
public:
22+
using FieldExtractor = std::function<absl::optional<std::string>(const Formatter::Context&,
23+
const StreamInfo::StreamInfo&)>;
24+
25+
DnsFormatterProvider(FieldExtractor field_extractor)
26+
: field_extractor_(std::move(field_extractor)) {}
27+
28+
// FormatterProvider
29+
absl::optional<std::string> format(const Formatter::Context& context,
30+
const StreamInfo::StreamInfo& stream_info) const override {
31+
return field_extractor_(context, stream_info);
32+
}
33+
34+
Protobuf::Value formatValue(const Formatter::Context& context,
35+
const StreamInfo::StreamInfo& stream_info) const override {
36+
const auto str = field_extractor_(context, stream_info);
37+
return str.has_value() ? ValueUtil::stringValue(str.value()) : ValueUtil::nullValue();
38+
}
39+
40+
private:
41+
const FieldExtractor field_extractor_;
42+
};
43+
44+
/**
45+
* Helper to create formatter provider for query-dependent fields (fields that require queries_[0]).
46+
*/
47+
template <typename FieldAccessor>
48+
Formatter::FormatterProviderPtr makeQueryFieldProvider(FieldAccessor accessor) {
49+
return std::make_unique<DnsFormatterProvider>(
50+
[accessor](const Formatter::Context& ctx,
51+
const StreamInfo::StreamInfo&) -> absl::optional<std::string> {
52+
const auto dns_ctx = ctx.typedExtension<DnsQueryContext>();
53+
if (!dns_ctx.has_value() || dns_ctx->queries_.empty()) {
54+
return absl::nullopt;
55+
}
56+
return absl::StrCat(accessor(*dns_ctx));
57+
});
58+
}
59+
60+
/**
61+
* Helper to create formatter provider for context-level fields (fields that don't require queries).
62+
*/
63+
template <typename FieldAccessor>
64+
Formatter::FormatterProviderPtr makeContextFieldProvider(FieldAccessor accessor) {
65+
return std::make_unique<DnsFormatterProvider>(
66+
[accessor](const Formatter::Context& ctx,
67+
const StreamInfo::StreamInfo&) -> absl::optional<std::string> {
68+
const auto dns_ctx = ctx.typedExtension<DnsQueryContext>();
69+
if (!dns_ctx.has_value()) {
70+
return absl::nullopt;
71+
}
72+
return accessor(*dns_ctx);
73+
});
74+
}
75+
76+
/**
77+
* DNS Filter command parser implementation.
78+
*/
79+
class DnsFilterCommandParser : public Formatter::CommandParser {
80+
public:
81+
using ProviderFunc =
82+
std::function<Formatter::FormatterProviderPtr(absl::string_view, absl::optional<size_t>)>;
83+
using ProviderFuncTable = absl::flat_hash_map<std::string, ProviderFunc>;
84+
85+
// CommandParser
86+
Formatter::FormatterProviderPtr parse(absl::string_view command, absl::string_view command_arg,
87+
absl::optional<size_t> max_length) const override {
88+
const auto& provider_table = providerFuncTable();
89+
const auto func_it = provider_table.find(std::string(command));
90+
if (func_it == provider_table.end()) {
91+
return nullptr;
92+
}
93+
return func_it->second(command_arg, max_length);
94+
}
95+
96+
private:
97+
static const ProviderFuncTable& providerFuncTable() {
98+
CONSTRUCT_ON_FIRST_USE(
99+
ProviderFuncTable,
100+
{
101+
{"QUERY_NAME",
102+
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
103+
return makeQueryFieldProvider(
104+
[](const DnsQueryContext& ctx) { return ctx.queries_[0]->name_; });
105+
}},
106+
{"QUERY_TYPE",
107+
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
108+
return makeQueryFieldProvider(
109+
[](const DnsQueryContext& ctx) { return ctx.queries_[0]->type_; });
110+
}},
111+
{"QUERY_CLASS",
112+
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
113+
return makeQueryFieldProvider(
114+
[](const DnsQueryContext& ctx) { return ctx.queries_[0]->class_; });
115+
}},
116+
{"ANSWER_COUNT",
117+
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
118+
return makeContextFieldProvider(
119+
[](const DnsQueryContext& ctx) { return absl::StrCat(ctx.answers_.size()); });
120+
}},
121+
{"RESPONSE_CODE",
122+
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
123+
return makeContextFieldProvider(
124+
[](const DnsQueryContext& ctx) { return absl::StrCat(ctx.response_code_); });
125+
}},
126+
{"PARSE_STATUS",
127+
[](absl::string_view, absl::optional<size_t>) -> Formatter::FormatterProviderPtr {
128+
return makeContextFieldProvider([](const DnsQueryContext& ctx) -> std::string {
129+
return ctx.parse_status_ ? "true" : "false";
130+
});
131+
}},
132+
});
133+
}
134+
};
135+
136+
} // namespace
137+
138+
Formatter::CommandParserPtr createDnsFilterCommandParser() {
139+
return std::make_unique<DnsFilterCommandParser>();
140+
}
141+
142+
} // namespace DnsFilter
143+
} // namespace UdpFilters
144+
} // namespace Extensions
145+
} // namespace Envoy
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include "envoy/formatter/substitution_formatter_base.h"
4+
5+
namespace Envoy {
6+
namespace Extensions {
7+
namespace UdpFilters {
8+
namespace DnsFilter {
9+
10+
/**
11+
* Creates a DNS filter-specific command parser for access logging.
12+
* Supports custom format commands for DNS-specific attributes:
13+
* - QUERY_NAME: The DNS query name being resolved
14+
* - QUERY_TYPE: The DNS query type (A, AAAA, SRV, etc.)
15+
* - QUERY_CLASS: The DNS query class
16+
* - ANSWER_COUNT: Number of answers in the response
17+
* - RESPONSE_CODE: DNS response code
18+
* - PARSE_STATUS: Whether the query was successfully parsed
19+
*
20+
* @return CommandParserPtr DNS filter command parser
21+
*/
22+
Formatter::CommandParserPtr createDnsFilterCommandParser();
23+
24+
} // namespace DnsFilter
25+
} // namespace UdpFilters
26+
} // namespace Extensions
27+
} // namespace Envoy

0 commit comments

Comments
 (0)