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
6 changes: 3 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = [".", "lazydns-macros"]

[package]
name = "lazydns"
version = "0.3.12"
version = "0.3.13"
edition = "2024"
authors = ["lazywalker <lazywalkerz@gmail.com>"]
description = "A light and fast DNS server/forwarder implementation in Rust"
Expand Down
2 changes: 0 additions & 2 deletions examples/etc/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,6 @@ plugins:
# or use the upstream_reject defined above
# exec: $upstream_reject

- exec: prefer_ipv4

- exec: $redirect

# skip cache for dynamic domains, others use cache
Expand Down
67 changes: 63 additions & 4 deletions src/plugins/flow/prefer_ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,23 @@ impl Plugin for PreferIpv4Plugin {
}

async fn execute(&self, ctx: &mut crate::plugin::Context) -> crate::Result<()> {
if let Some(response) = ctx.response_mut() {
// Only filter AAAA records if the query type is A
// If query type is AAAA, we should NOT filter AAAA records
let should_filter = ctx
.request()
.questions()
.first()
.map(|q| q.qtype() == RecordType::A)
.unwrap_or(false);

if should_filter && let Some(response) = ctx.response_mut() {
let answers = response.answers_mut();
answers.retain(|record| !matches!(record.rtype(), RecordType::AAAA));

let additional = response.additional_mut();
additional.retain(|record| !matches!(record.rtype(), RecordType::AAAA));
}

Ok(())
}
}
Expand All @@ -47,15 +57,19 @@ impl ExecPlugin for PreferIpv4Plugin {
#[cfg(test)]
mod tests {
use super::*;
use crate::dns::{Message, RData, RecordClass, ResourceRecord};
use crate::dns::{Message, Question, RData, RecordClass, ResourceRecord};

#[tokio::test]
async fn test_prefer_ipv4_plugin() {
async fn test_prefer_ipv4_plugin_filters_on_a_query() {
let plugin = PreferIpv4Plugin::new();
assert_eq!(plugin.name(), "prefer_ipv4");

let mut ctx = crate::plugin::Context::new(Message::new());
// Test with A query - should filter AAAA records
let mut request = Message::new();
request.add_question(Question::new("example.com", RecordType::A, RecordClass::IN));

let mut response = Message::new();
response.add_question(Question::new("example.com", RecordType::A, RecordClass::IN));

response.add_answer(ResourceRecord::new(
"example.com",
Expand All @@ -73,11 +87,56 @@ mod tests {
RData::AAAA("2001:db8::1".parse().unwrap()),
));

let mut ctx = crate::plugin::Context::new(request);
ctx.set_response(Some(response));
plugin.execute(&mut ctx).await.unwrap();

let response = ctx.response().unwrap();
assert_eq!(response.answers().len(), 1);
assert!(matches!(response.answers()[0].rtype(), RecordType::A));
}

#[tokio::test]
async fn test_prefer_ipv4_plugin_preserves_on_aaaa_query() {
let plugin = PreferIpv4Plugin::new();

// Test with AAAA query - should NOT filter AAAA records
let mut request = Message::new();
request.add_question(Question::new(
"example.com",
RecordType::AAAA,
RecordClass::IN,
));

let mut response = Message::new();
response.add_question(Question::new(
"example.com",
RecordType::AAAA,
RecordClass::IN,
));

response.add_answer(ResourceRecord::new(
"example.com",
RecordType::A,
RecordClass::IN,
300,
RData::A("192.0.2.1".parse().unwrap()),
));

response.add_answer(ResourceRecord::new(
"example.com",
RecordType::AAAA,
RecordClass::IN,
300,
RData::AAAA("2001:db8::1".parse().unwrap()),
));

let mut ctx = crate::plugin::Context::new(request);
ctx.set_response(Some(response));
plugin.execute(&mut ctx).await.unwrap();

let response = ctx.response().unwrap();
// Both records should be preserved when query type is AAAA
assert_eq!(response.answers().len(), 2);
}
}
76 changes: 71 additions & 5 deletions src/plugins/flow/prefer_ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,23 @@ impl Plugin for PreferIpv6Plugin {
}

async fn execute(&self, ctx: &mut crate::plugin::Context) -> crate::Result<()> {
if let Some(response) = ctx.response_mut() {
// Only filter A records if the query type is AAAA
// If query type is A, we should NOT filter A records
let should_filter = ctx
.request()
.questions()
.first()
.map(|q| q.qtype() == RecordType::AAAA)
.unwrap_or(false);

if should_filter && let Some(response) = ctx.response_mut() {
let answers = response.answers_mut();
answers.retain(|record| !matches!(record.rtype(), RecordType::A));

let additional = response.additional_mut();
additional.retain(|record| !matches!(record.rtype(), RecordType::A));
}

Ok(())
}
}
Expand All @@ -47,15 +57,27 @@ impl ExecPlugin for PreferIpv6Plugin {
#[cfg(test)]
mod tests {
use super::*;
use crate::dns::{Message, RData, RecordClass, ResourceRecord};
use crate::dns::{Message, Question, RData, RecordClass, ResourceRecord};

#[tokio::test]
async fn test_prefer_ipv6_plugin() {
async fn test_prefer_ipv6_plugin_filters_on_aaaa_query() {
let plugin = PreferIpv6Plugin::new();
assert_eq!(plugin.name(), "prefer_ipv6");

let mut ctx = crate::plugin::Context::new(Message::new());
// Test with AAAA query - should filter A records
let mut request = Message::new();
request.add_question(Question::new(
"example.com",
RecordType::AAAA,
RecordClass::IN,
));

let mut response = Message::new();
response.add_question(Question::new(
"example.com",
RecordType::AAAA,
RecordClass::IN,
));

response.add_answer(ResourceRecord::new(
"example.com",
Expand All @@ -73,6 +95,7 @@ mod tests {
RData::AAAA("2001:db8::1".parse().unwrap()),
));

let mut ctx = crate::plugin::Context::new(request);
ctx.set_response(Some(response));
plugin.execute(&mut ctx).await.unwrap();

Expand All @@ -81,6 +104,42 @@ mod tests {
assert!(matches!(response.answers()[0].rtype(), RecordType::AAAA));
}

#[tokio::test]
async fn test_prefer_ipv6_plugin_preserves_on_a_query() {
let plugin = PreferIpv6Plugin::new();

// Test with A query - should NOT filter A records
let mut request = Message::new();
request.add_question(Question::new("example.com", RecordType::A, RecordClass::IN));

let mut response = Message::new();
response.add_question(Question::new("example.com", RecordType::A, RecordClass::IN));

response.add_answer(ResourceRecord::new(
"example.com",
RecordType::A,
RecordClass::IN,
300,
RData::A("192.0.2.1".parse().unwrap()),
));

response.add_answer(ResourceRecord::new(
"example.com",
RecordType::AAAA,
RecordClass::IN,
300,
RData::AAAA("2001:db8::1".parse().unwrap()),
));

let mut ctx = crate::plugin::Context::new(request);
ctx.set_response(Some(response));
plugin.execute(&mut ctx).await.unwrap();

let response = ctx.response().unwrap();
// Both records should be preserved when query type is A
assert_eq!(response.answers().len(), 2);
}

#[tokio::test]
async fn test_prefer_ipv6_no_response() {
let plugin = PreferIpv6Plugin::new();
Expand All @@ -94,7 +153,14 @@ mod tests {
#[tokio::test]
async fn test_prefer_ipv6_additional_records() {
let plugin = PreferIpv6Plugin::new();
let mut ctx = crate::plugin::Context::new(Message::new());
let mut request = Message::new();
request.add_question(Question::new(
"ns.example.com",
RecordType::AAAA,
RecordClass::IN,
));

let mut ctx = crate::plugin::Context::new(request);
let mut response = Message::new();

response.add_additional(ResourceRecord::new(
Expand Down