Skip to content

Commit d787c4a

Browse files
committed
cleanuP
1 parent 3e4d510 commit d787c4a

File tree

5 files changed

+255
-174
lines changed

5 files changed

+255
-174
lines changed
Lines changed: 135 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
1-
//! UDP listener filter for DNS-based routing with virtual IP allocation.
2-
//!
3-
//! This filter:
4-
//! 1. Listens on port 5353 for DNS queries
5-
//! 2. Parses DNS A record queries
6-
//! 3. Matches domains against configured policies
7-
//! 4. Allocates virtual IPs via VirtualIpCache
8-
//! 5. Returns DNS A responses
9-
101
use envoy_proxy_dynamic_modules_rust_sdk::*;
11-
use std::net::Ipv4Addr;
122
use hickory_proto::op::{Message, MessageType, ResponseCode};
133
use hickory_proto::rr::{Name, RData, Record, RecordType};
144
use hickory_proto::serialize::binary::{BinDecodable, BinDecoder};
5+
use std::net::Ipv4Addr;
156

167
use super::virtual_ip_cache::{get_cache, init_cache, EgressPolicy};
178

18-
/// DNS Gateway filter configuration.
199
pub struct DnsGatewayFilterConfig {
2010
policies: Vec<PolicyMatcher>,
2111
}
@@ -35,13 +25,11 @@ impl PolicyMatcher {
3525
let suffix = &self.domain_pattern[2..];
3626
domain.ends_with(suffix)
3727
} else {
38-
// Exact match
39-
domain == self.domain_pattern
28+
domain == self.domain_pattern // Exact match
4029
}
4130
}
4231
}
4332

44-
/// Creates a new DNS gateway filter configuration.
4533
pub fn new_udp_filter_config<EC: EnvoyUdpListenerFilterConfig, ELF: EnvoyUdpListenerFilter>(
4634
_envoy_filter_config: &mut EC,
4735
_name: &str,
@@ -64,15 +52,13 @@ pub fn new_udp_filter_config<EC: EnvoyUdpListenerFilterConfig, ELF: EnvoyUdpList
6452
};
6553

6654
// Parse base_ip from config (default: 10.10.0.0)
67-
let base_ip_str = config_json["base_ip"]
68-
.as_str()
69-
.unwrap_or("10.10.0.0");
55+
let base_ip_str = config_json["base_ip"].as_str().unwrap_or("10.10.0.0");
7056

7157
let base_ip: Ipv4Addr = base_ip_str.parse().ok()?;
7258
let base_ip_u32 = u32::from(base_ip);
7359

7460
// Initialize the cache (first call wins, subsequent calls are ignored)
75-
let _ = init_cache(base_ip_u32);
61+
init_cache(base_ip_u32);
7662

7763
// Parse policies
7864
let policies_array = config_json["policies"].as_array()?;
@@ -115,7 +101,6 @@ impl<ELF: EnvoyUdpListenerFilter> UdpListenerFilterConfig<ELF> for DnsGatewayFil
115101
}
116102
}
117103

118-
/// DNS Gateway filter instance.
119104
struct DnsGatewayFilter {
120105
policies: Vec<PolicyMatcher>,
121106
}
@@ -125,20 +110,21 @@ impl<ELF: EnvoyUdpListenerFilter> UdpListenerFilter<ELF> for DnsGatewayFilter {
125110
&mut self,
126111
envoy_filter: &mut ELF,
127112
) -> abi::envoy_dynamic_module_type_on_udp_listener_filter_status {
128-
// Get datagram data
129113
let (chunks, total_length) = envoy_filter.get_datagram_data();
130-
envoy_log_info!("dns_gateway: received UDP datagram, {} bytes, {} chunks", total_length, chunks.len());
114+
envoy_log_info!(
115+
"dns_gateway: received UDP datagram, {} bytes, {} chunks",
116+
total_length,
117+
chunks.len()
118+
);
131119
let mut data = Vec::new();
132120
for chunk in &chunks {
133121
data.extend_from_slice(chunk.as_slice());
134122
}
135123

136-
// Get peer address for sending the response back
137124
let peer = envoy_filter.get_peer_address();
138125
envoy_log_info!("dns_gateway: peer address: {:?}", peer);
139126

140-
// Parse DNS query using hickory-dns
141-
let mut decoder = BinDecoder::new(&data);
127+
let mut decoder: BinDecoder<'_> = BinDecoder::new(&data);
142128
let query_message = match Message::read(&mut decoder) {
143129
Ok(msg) => msg,
144130
Err(e) => {
@@ -147,10 +133,13 @@ impl<ELF: EnvoyUdpListenerFilter> UdpListenerFilter<ELF> for DnsGatewayFilter {
147133
}
148134
};
149135

150-
envoy_log_info!("dns_gateway: parsed DNS message id={}, type={:?}, queries={}",
151-
query_message.id(), query_message.message_type(), query_message.queries().len());
136+
envoy_log_info!(
137+
"dns_gateway: parsed DNS message id={}, type={:?}, queries={}",
138+
query_message.id(),
139+
query_message.message_type(),
140+
query_message.queries().len()
141+
);
152142

153-
// Validate it's a query and has at least one question
154143
if query_message.message_type() != MessageType::Query {
155144
envoy_log_warn!("dns_gateway: received non-query DNS message");
156145
return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue;
@@ -164,123 +153,181 @@ impl<ELF: EnvoyUdpListenerFilter> UdpListenerFilter<ELF> for DnsGatewayFilter {
164153
}
165154
};
166155

167-
// Only handle A record queries
168-
if question.query_type() != RecordType::A {
169-
envoy_log_info!("dns_gateway: ignoring non-A record query: {:?}", question.query_type());
170-
return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue;
171-
}
172-
173156
let domain_raw = question.name().to_utf8();
174157
// DNS names are fully qualified with a trailing dot (e.g. "api.aws.com.").
175158
// Strip it so our wildcard patterns like "*.aws.com" match correctly.
176-
let domain = domain_raw.strip_suffix('.').unwrap_or(&domain_raw).to_string();
177-
envoy_log_info!("dns_gateway: A record query for domain: {} (raw: {})", domain, domain_raw);
159+
let domain = domain_raw
160+
.strip_suffix('.')
161+
.unwrap_or(&domain_raw)
162+
.to_string();
163+
164+
// Handle A and AAAA record queries
165+
match question.query_type() {
166+
RecordType::A => {
167+
envoy_log_info!(
168+
"dns_gateway: A record query for domain: {} (raw: {})",
169+
domain,
170+
domain_raw
171+
);
172+
}
173+
RecordType::AAAA => {
174+
envoy_log_info!(
175+
"dns_gateway: AAAA record query for domain: {} (raw: {})",
176+
domain,
177+
domain_raw
178+
);
179+
}
180+
_ => {
181+
envoy_log_info!(
182+
"dns_gateway: ignoring non-A/AAAA record query: {:?}",
183+
question.query_type()
184+
);
185+
return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue;
186+
}
187+
}
178188

179-
// Match against policies
180189
let matched_policy = self.policies.iter().find(|p| p.matches(&domain));
181190

182191
if let Some(policy_matcher) = matched_policy {
183-
envoy_log_info!("dns_gateway: matched policy pattern '{}' for domain '{}'",
184-
policy_matcher.domain_pattern, domain);
185-
186-
// Create EgressPolicy from matcher
187-
let policy = EgressPolicy {
188-
domain: domain.clone(),
189-
metadata: policy_matcher.metadata.clone(),
190-
};
191-
192-
// Allocate virtual IP via cache
193-
let cache = get_cache();
194-
let virtual_ip = cache.allocate(policy);
195-
196192
envoy_log_info!(
197-
"dns_gateway: allocated virtual IP {} for domain {}",
198-
virtual_ip,
193+
"dns_gateway: matched policy pattern '{}' for domain '{}'",
194+
policy_matcher.domain_pattern,
199195
domain
200196
);
201197

202-
// Craft DNS A response using hickory-dns
203-
let response_bytes = match craft_dns_response(&query_message, question.name(), virtual_ip) {
204-
Ok(bytes) => {
205-
envoy_log_info!("dns_gateway: crafted DNS response, {} bytes", bytes.len());
206-
bytes
198+
// Craft the appropriate response based on query type
199+
let response_bytes = match question.query_type() {
200+
RecordType::A => {
201+
// Allocate virtual IP for A record queries
202+
let policy = EgressPolicy {
203+
domain: domain.clone(),
204+
metadata: policy_matcher.metadata.clone(),
205+
};
206+
207+
let cache = get_cache();
208+
let virtual_ip = cache.allocate(policy);
209+
210+
envoy_log_info!(
211+
"dns_gateway: allocated virtual IP {} for domain {}",
212+
virtual_ip,
213+
domain
214+
);
215+
216+
// Craft DNS A response with virtual IP
217+
match build_dns_response(&query_message, question.name(), virtual_ip) {
218+
Ok(bytes) => {
219+
envoy_log_info!(
220+
"dns_gateway: crafted DNS A response, {} bytes",
221+
bytes.len()
222+
);
223+
bytes
224+
}
225+
Err(e) => {
226+
envoy_log_error!("dns_gateway: failed to craft DNS A response: {}", e);
227+
return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue;
228+
}
229+
}
207230
}
208-
Err(e) => {
209-
envoy_log_error!("dns_gateway: failed to craft DNS response: {}", e);
231+
RecordType::AAAA => {
232+
// Return NODATA response for AAAA queries (no IPv6 available)
233+
envoy_log_info!(
234+
"dns_gateway: returning NODATA for AAAA query (no IPv6 available)"
235+
);
236+
237+
match build_nodata_response(&query_message) {
238+
Ok(bytes) => {
239+
envoy_log_info!(
240+
"dns_gateway: crafted NODATA response, {} bytes",
241+
bytes.len()
242+
);
243+
bytes
244+
}
245+
Err(e) => {
246+
envoy_log_error!("dns_gateway: failed to craft NODATA response: {}", e);
247+
return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue;
248+
}
249+
}
250+
}
251+
_ => {
252+
// Should never reach here due to earlier match
210253
return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue;
211254
}
212255
};
213256

214-
// Send the DNS response back to the peer directly.
215-
// Note: set_datagram_data() only modifies the buffer in-place but doesn't
216-
// send it back. We must use send_datagram() to actually reply to the client.
217257
if let Some((peer_addr, peer_port)) = peer {
218-
envoy_log_info!("dns_gateway: sending {} byte response to {}:{}", response_bytes.len(), peer_addr, peer_port);
258+
envoy_log_info!(
259+
"dns_gateway: sending {} byte response to {}:{}",
260+
response_bytes.len(),
261+
peer_addr,
262+
peer_port
263+
);
219264
if !envoy_filter.send_datagram(&response_bytes, &peer_addr, peer_port) {
220-
envoy_log_error!("dns_gateway: failed to send datagram to {}:{}", peer_addr, peer_port);
265+
envoy_log_error!(
266+
"dns_gateway: failed to send datagram to {}:{}",
267+
peer_addr,
268+
peer_port
269+
);
221270
}
222271
} else {
223272
envoy_log_error!("dns_gateway: no peer address available, cannot send response");
224273
}
225274

226-
// StopIteration prevents the udp_proxy filter from also forwarding this packet
227275
return abi::envoy_dynamic_module_type_on_udp_listener_filter_status::StopIteration;
228276
} else {
229277
envoy_log_info!("dns_gateway: no policy matched for domain: {}", domain);
230278
}
231279

232-
// No match — let the packet continue to the next filter (udp_proxy)
233280
abi::envoy_dynamic_module_type_on_udp_listener_filter_status::Continue
234281
}
235282
}
236283

237-
/// Crafts a DNS A response with the given virtual IP using hickory-dns.
238-
///
239-
/// This function creates a proper DNS response message with:
240-
/// - The original query's transaction ID
241-
/// - Standard query response flags
242-
/// - The original question section
243-
/// - An answer section with the allocated virtual IP
244-
///
245-
/// # Arguments
246-
/// * `query_message` - The original DNS query message
247-
/// * `name` - The domain name being queried
248-
/// * `ip` - The virtual IP address to return
249-
///
250-
/// # Returns
251-
/// The serialized DNS response as bytes, or an error if serialization fails
252-
fn craft_dns_response(
284+
fn build_dns_response(
253285
query_message: &Message,
254286
name: &Name,
255287
ip: Ipv4Addr,
256288
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
257289
let mut response = Message::new();
258290

259-
// Copy transaction ID from query
260291
response.set_id(query_message.id());
261292

262-
// Set response flags
263293
response.set_message_type(MessageType::Response);
264294
response.set_response_code(ResponseCode::NoError);
265295
response.set_recursion_desired(query_message.recursion_desired());
266296
response.set_recursion_available(true);
267297

268-
// Add the original question
269298
if let Some(question) = query_message.queries().first() {
270299
response.add_query(question.clone());
271300
}
272301

273-
// Create answer record
274302
let mut record = Record::new();
275303
record.set_name(name.clone());
276304
record.set_record_type(RecordType::A);
277-
record.set_ttl(60); // 60 seconds TTL
305+
record.set_ttl(600); // 10 min TTL
278306
record.set_data(Some(RData::A(ip.into())));
279307

280-
// Add answer to response
281308
response.add_answer(record);
282309

283-
// Serialize to bytes
310+
let bytes = response.to_vec()?;
311+
Ok(bytes)
312+
}
313+
314+
// For now, no IPv6 support
315+
fn build_nodata_response(query_message: &Message) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
316+
let mut response = Message::new();
317+
318+
response.set_id(query_message.id());
319+
320+
response.set_message_type(MessageType::Response);
321+
response.set_response_code(ResponseCode::NoError);
322+
response.set_recursion_desired(query_message.recursion_desired());
323+
response.set_recursion_available(true);
324+
325+
if let Some(question) = query_message.queries().first() {
326+
response.add_query(question.clone());
327+
}
328+
329+
// No answer records - this is a NODATA response
330+
284331
let bytes = response.to_vec()?;
285332
Ok(bytes)
286333
}

0 commit comments

Comments
 (0)