From af0a365b43a7ae63b1d937d1b506159df463c985 Mon Sep 17 00:00:00 2001 From: AURYLA Evaldas Date: Fri, 5 Mar 2021 22:24:15 +0100 Subject: [PATCH] Add EDNS Client Subnet option in dns query --- example-encrypted-dns.toml | 14 ++++++ src/config.rs | 8 ++++ src/dns.rs | 15 +++--- src/globals.rs | 3 ++ src/main.rs | 17 +++++++ src/resolver.rs | 94 ++++++++++++++++++++++++++++++++++---- 6 files changed, 135 insertions(+), 16 deletions(-) diff --git a/example-encrypted-dns.toml b/example-encrypted-dns.toml index d992640..76e1339 100644 --- a/example-encrypted-dns.toml +++ b/example-encrypted-dns.toml @@ -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 + diff --git a/src/config.rs b/src/config.rs index f3c9776..f5e045b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, @@ -87,6 +94,7 @@ pub struct Config { pub metrics: Option, pub anonymized_dns: Option, pub access_control: Option, + pub ecs: Option, } impl Config { diff --git a/src/dns.rs b/src/dns.rs index 719817b..9117e27 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -403,8 +403,8 @@ pub fn set_ttl(packet: &mut [u8], ttl: u32) -> Result<(), Error> { Ok(()) } -fn add_edns_section(packet: &mut Vec, max_payload_size: u16) -> Result<(), Error> { - let opt_rr: [u8; 11] = [ +fn add_edns_section(packet: &mut Vec, max_payload_size: u16, opt_rdata: &Vec) -> Result<(), Error> { + let mut opt_rr = vec![ 0, (DNS_TYPE_OPT >> 8) as u8, DNS_TYPE_OPT as u8, @@ -413,10 +413,11 @@ fn add_edns_section(packet: &mut Vec, 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" @@ -426,7 +427,7 @@ fn add_edns_section(packet: &mut Vec, max_payload_size: u16) -> Result<(), E Ok(()) } -pub fn set_edns_max_payload_size(packet: &mut Vec, max_payload_size: u16) -> Result<(), Error> { +pub fn set_edns_max_payload_size(packet: &mut Vec, max_payload_size: u16, opt_rdata: &Vec) -> Result<(), Error> { let packet_len = packet.len(); ensure!(packet_len > DNS_OFFSET_QUESTION, "Short packet"); ensure!(packet_len <= DNS_MAX_PACKET_SIZE, "Large packet"); @@ -455,7 +456,7 @@ pub fn set_edns_max_payload_size(packet: &mut Vec, 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(()) } diff --git a/src/globals.rs b/src/globals.rs index c855fdc..0ed8593 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -49,6 +49,9 @@ pub struct Globals { pub anonymized_dns_allow_non_reserved_ports: bool, pub anonymized_dns_blacklisted_ips: Vec, pub access_control_tokens: Option>, + 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>, #[cfg(feature = "metrics")] diff --git a/src/main.rs b/src/main.rs index 034ff81..86653bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(), @@ -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")] diff --git a/src/resolver.rs b/src/resolver.rs index facd3e4..fbdd6db 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -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}; @@ -18,6 +18,7 @@ pub async fn resolve_udp( mut packet: &mut Vec, packet_qname: &[u8], tid: u16, + opt_rdata: &Vec, has_cached_response: bool, ) -> Result, Error> { let ext_socket = match globals.external_addr { @@ -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 @@ -76,9 +77,10 @@ pub async fn resolve_udp( pub async fn resolve_tcp( globals: &Globals, - packet: &mut Vec, + mut packet: &mut Vec, packet_qname: &[u8], tid: u16, + opt_rdata: &Vec, ) -> Result, Error> { let socket = match globals.external_addr { Some(x @ SocketAddr::V4(_)) => { @@ -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?; @@ -128,6 +131,7 @@ pub async fn resolve( cached_response: Option, packet_hash: u128, original_tid: u16, + opt_rdata: &Vec, ) -> Result, Error> { #[cfg(feature = "metrics")] globals.varz.upstream_sent.inc(); @@ -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")] { @@ -186,13 +191,13 @@ pub async fn get_cached_response_or_resolve( mut packet: &mut Vec, ) -> Result, 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); } } @@ -260,6 +265,76 @@ 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, @@ -267,6 +342,7 @@ pub async fn get_cached_response_or_resolve( cached_response, packet_hash, original_tid, + &opt_rdata, ) .await }