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

Edns ecs #59

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
14 changes: 14 additions & 0 deletions example-encrypted-dns.toml
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,17 @@ enabled = false

tokens = ["Y2oHkDJNHz", "G5zY3J5cHQtY", "C5zZWN1cmUuZG5z"]


################################
# EDNS Client Subnet Option #
################################

[ecs]

# Enable to send to upstream client subnet data in query packet, RFC6891
enabled = false

# RFC6891
source_prefix_ipv4 = 24
source_prefix_ipv6 = 64

8 changes: 8 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ use std::net::{IpAddr, SocketAddr};
use std::path::{Path, PathBuf};
use tokio::io::AsyncWriteExt;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ECSConfig {
pub enabled: bool,
pub source_prefix_ipv4: u8,
pub source_prefix_ipv6: u8,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AccessControlConfig {
pub enabled: bool,
Expand Down Expand Up @@ -87,6 +94,7 @@ pub struct Config {
pub metrics: Option<MetricsConfig>,
pub anonymized_dns: Option<AnonymizedDNSConfig>,
pub access_control: Option<AccessControlConfig>,
pub ecs: Option<ECSConfig>,
}

impl Config {
Expand Down
15 changes: 8 additions & 7 deletions src/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,8 @@ pub fn set_ttl(packet: &mut [u8], ttl: u32) -> Result<(), Error> {
Ok(())
}

fn add_edns_section(packet: &mut Vec<u8>, max_payload_size: u16) -> Result<(), Error> {
let opt_rr: [u8; 11] = [
fn add_edns_section(packet: &mut Vec<u8>, max_payload_size: u16, opt_rdata: &Vec<u8>) -> Result<(), Error> {
let mut opt_rr = vec![
0,
(DNS_TYPE_OPT >> 8) as u8,
DNS_TYPE_OPT as u8,
Expand All @@ -413,10 +413,11 @@ fn add_edns_section(packet: &mut Vec<u8>, max_payload_size: u16) -> Result<(), E
0,
0,
0,
0,
0,
0,
0
];
for i in opt_rdata.iter() {
opt_rr.push(*i);
}
ensure!(
DNS_MAX_PACKET_SIZE - packet.len() >= opt_rr.len(),
"Packet would be too large to add a new record"
Expand All @@ -426,7 +427,7 @@ fn add_edns_section(packet: &mut Vec<u8>, max_payload_size: u16) -> Result<(), E
Ok(())
}

pub fn set_edns_max_payload_size(packet: &mut Vec<u8>, max_payload_size: u16) -> Result<(), Error> {
pub fn set_edns_max_payload_size(packet: &mut Vec<u8>, max_payload_size: u16, opt_rdata: &Vec<u8>) -> Result<(), Error> {
let packet_len = packet.len();
ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet");
ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet");
Expand Down Expand Up @@ -455,7 +456,7 @@ pub fn set_edns_max_payload_size(packet: &mut Vec<u8>, max_payload_size: u16) ->
if edns_payload_set {
return Ok(());
}
add_edns_section(packet, max_payload_size)?;
add_edns_section(packet, max_payload_size, opt_rdata)?;
Ok(())
}

Expand Down
3 changes: 3 additions & 0 deletions src/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ pub struct Globals {
pub anonymized_dns_allow_non_reserved_ports: bool,
pub anonymized_dns_blacklisted_ips: Vec<IpAddr>,
pub access_control_tokens: Option<Vec<String>>,
pub ecs_enabled: bool,
pub ecs_source_prefix_ipv4: u8,
pub ecs_source_prefix_ipv6: u8,
pub client_ttl_holdon: u32,
pub my_ip: Option<Vec<u8>>,
#[cfg(feature = "metrics")]
Expand Down
17 changes: 17 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,20 @@ fn main() -> Result<(), Error> {
}
_ => None,
};

let (
ecs_enabled,
ecs_source_prefix_ipv4,
ecs_source_prefix_ipv6,
) = match config.ecs {
None => (false, 0, 0),
Some(ecs) => (
ecs.enabled,
ecs.source_prefix_ipv4,
ecs.source_prefix_ipv6,
),
};

let runtime_handle = runtime.handle();
let globals = Arc::new(Globals {
runtime_handle: runtime_handle.clone(),
Expand Down Expand Up @@ -736,6 +750,9 @@ fn main() -> Result<(), Error> {
anonymized_dns_allow_non_reserved_ports,
anonymized_dns_blacklisted_ips,
access_control_tokens,
ecs_enabled,
ecs_source_prefix_ipv4,
ecs_source_prefix_ipv6,
my_ip: config.my_ip.map(|ip| ip.as_bytes().to_ascii_lowercase()),
client_ttl_holdon: config.client_ttl_holdon.unwrap_or(60),
#[cfg(feature = "metrics")]
Expand Down
94 changes: 85 additions & 9 deletions src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rand::prelude::*;
use siphasher::sip128::Hasher128;
use std::cmp;
use std::hash::Hasher;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpSocket, UdpSocket};

Expand All @@ -18,6 +18,7 @@ pub async fn resolve_udp(
mut packet: &mut Vec<u8>,
packet_qname: &[u8],
tid: u16,
opt_rdata: &Vec<u8>,
has_cached_response: bool,
) -> Result<Vec<u8>, Error> {
let ext_socket = match globals.external_addr {
Expand All @@ -39,7 +40,7 @@ pub async fn resolve_udp(
},
};
ext_socket.connect(globals.upstream_addr).await?;
dns::set_edns_max_payload_size(&mut packet, DNS_MAX_PACKET_SIZE as u16)?;
dns::set_edns_max_payload_size(&mut packet, DNS_MAX_PACKET_SIZE as u16, opt_rdata)?;
let mut response;
let timeout = if has_cached_response {
globals.udp_timeout / 2
Expand Down Expand Up @@ -76,9 +77,10 @@ pub async fn resolve_udp(

pub async fn resolve_tcp(
globals: &Globals,
packet: &mut Vec<u8>,
mut packet: &mut Vec<u8>,
packet_qname: &[u8],
tid: u16,
opt_rdata: &Vec<u8>,
) -> Result<Vec<u8>, Error> {
let socket = match globals.external_addr {
Some(x @ SocketAddr::V4(_)) => {
Expand All @@ -100,6 +102,7 @@ pub async fn resolve_tcp(
};
let mut ext_socket = socket.connect(globals.upstream_addr).await?;
ext_socket.set_nodelay(true)?;
dns::set_edns_max_payload_size(&mut packet, DNS_MAX_PACKET_SIZE as u16, opt_rdata)?;
let mut binlen = [0u8, 0];
BigEndian::write_u16(&mut binlen[..], packet.len() as u16);
ext_socket.write_all(&binlen).await?;
Expand Down Expand Up @@ -128,6 +131,7 @@ pub async fn resolve(
cached_response: Option<CachedResponse>,
packet_hash: u128,
original_tid: u16,
opt_rdata: &Vec<u8>,
) -> Result<Vec<u8>, Error> {
#[cfg(feature = "metrics")]
globals.varz.upstream_sent.inc();
Expand All @@ -138,11 +142,12 @@ pub async fn resolve(
packet,
&packet_qname,
tid,
opt_rdata,
cached_response.is_some(),
)
.await?;
if dns::is_truncated(&response) {
response = resolve_tcp(globals, packet, &packet_qname, tid).await?;
response = resolve_tcp(globals, packet, &packet_qname, tid, opt_rdata).await?;
}
#[cfg(feature = "metrics")]
{
Expand Down Expand Up @@ -186,13 +191,13 @@ pub async fn get_cached_response_or_resolve(
mut packet: &mut Vec<u8>,
) -> Result<Vec<u8>, Error> {
let packet_qname = dns::qname(&packet)?;
let client_ip = match client_ctx {
ClientCtx::Udp(u) => u.client_addr,
ClientCtx::Tcp(t) => t.client_connection.peer_addr()?,
}
.ip();
if let Some(my_ip) = &globals.my_ip {
if &packet_qname.to_ascii_lowercase() == my_ip {
let client_ip = match client_ctx {
ClientCtx::Udp(u) => u.client_addr,
ClientCtx::Tcp(t) => t.client_connection.peer_addr()?,
}
.ip();
return serve_ip_response(packet.to_vec(), client_ip, 1);
}
}
Expand Down Expand Up @@ -260,13 +265,84 @@ pub async fn get_cached_response_or_resolve(
Some(cached_response)
}
};

let mut opt_rdata = vec![
0, // 2 octets, all RDATA length https://tools.ietf.org/html/rfc6891#page-7
0
];
if globals.ecs_enabled {
opt_rdata.push(0); // (Defined in [RFC6891]) OPTION-CODE, 2 octets
opt_rdata.push(8); // for ECS is 8 (0x00 0x08)
opt_rdata.push(0); // (Defined in [RFC6891]) OPTION-LENGTH, 2 octets
opt_rdata.push(8); // contains length of the payload (everything after OPTION-LENGTH) in octets.
opt_rdata.push(0); // FAMILY, 2 octets
opt_rdata.push(1); // (0x00 0x01 ipv4)
// SOURCE PREFIX-LENGTH, an unsigned octet representing the leftmost number of significant bits of ADDRESS
// SCOPE PREFIX-LENGTH, an unsigned octet representing.. In queries, it MUST be set to 0
// ADDRESS, variable number of octets, contains either an IPv4 or
// IPv6 address, depending on FAMILY, which MUST be truncated to the
// number of bits indicated by the SOURCE PREFIX-LENGTH field,
// padding with 0 bits to pad to the end of the last octet needed
let mut iplen:usize;
match client_ip {
IpAddr::V4(ipv4) => {
opt_rdata.push(globals.ecs_source_prefix_ipv4);
opt_rdata.push(0);
let mut mask:u32 = 0xFFFFFFFF;
let mut n = 32 - globals.ecs_source_prefix_ipv4;
while n > 0 {
mask <<= 1;
n -= 1;
}
let ipnum = u32::from(ipv4) & mask;
let iparr: [u8; 4] = ipnum.to_be_bytes();
iplen = iparr.len();
while iplen > 1 && iparr[iplen-1] == 0 {
if iparr[iplen-2] != 0 { break; }
iplen -= 1;
}
let mut z = 0;
while z < iplen {
opt_rdata.push(iparr[z]);
z += 1;
}
}
IpAddr::V6(ipv6) => {
opt_rdata.push(globals.ecs_source_prefix_ipv6);
opt_rdata.push(0);
opt_rdata[7] = 2; // family number 2 = ipv6
let mut mask:u128 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
let mut n = 128 - globals.ecs_source_prefix_ipv6;
while n > 0 {
mask <<= 1;
n -= 1;
}
let ipnum = u128::from(ipv6) & mask;
let iparr: [u8; 16] = ipnum.to_be_bytes();
iplen = iparr.len();
while iplen > 1 && iparr[iplen-1] == 0 {
if iparr[iplen-2] != 0 { break; }
iplen -= 1;
}
let mut z = 0;
while z < iplen {
opt_rdata.push(iparr[z]);
z += 1;
}
}
}
opt_rdata[1] = 8 + iplen as u8;
}
//info!("ECS data {:?}", opt_rdata);

resolve(
globals,
packet,
packet_qname,
cached_response,
packet_hash,
original_tid,
&opt_rdata,
)
.await
}