-
Notifications
You must be signed in to change notification settings - Fork 354
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite parts of the Encrypted DNS proxy parsing and handling
- Loading branch information
Showing
6 changed files
with
406 additions
and
376 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,129 +1,117 @@ | ||
//! Parse and use various proxy configurations as they are retrieved via AAAA records, hopefully | ||
//! served by DoH resolvers. | ||
use std::{ | ||
io::Cursor, | ||
net::{Ipv6Addr, SocketAddrV4}, | ||
}; | ||
use byteorder::{LittleEndian, ReadBytesExt}; | ||
use core::fmt; | ||
use std::net::{Ipv6Addr, SocketAddrV4}; | ||
|
||
mod plain; | ||
mod xor; | ||
pub use plain::Plain; | ||
pub use xor::Xor; | ||
|
||
/// An error that happens when parsing IPv6 addresses into proxy configurations. | ||
#[derive(Debug)] | ||
pub use xor::XorKey; | ||
|
||
#[derive(Debug, Eq, PartialEq)] | ||
pub enum Error { | ||
/// IP address representing a Xor proxy was not valid | ||
InvalidXor(xor::Error), | ||
/// IP address representing the plain proxy was not valid | ||
InvalidPlain(plain::Error), | ||
/// IP addresses did not contain any valid proxy configuration | ||
NoProxies, | ||
/// The proxy type field has a value this library is not compatible with | ||
UnknownProxyType(u16), | ||
/// The XorV1 proxy type is deprecated and not supported | ||
XorV1Unsupported, | ||
/// The port is not valid | ||
InvalidPort(u16), | ||
/// The key to use for XOR obfuscation was empty (all zeros) | ||
EmptyXorKey, | ||
} | ||
|
||
/// If a given IPv6 address does not contain a valid value for the proxy version, this error type | ||
/// will contain the unrecognized value. | ||
#[derive(Debug)] | ||
pub struct ErrorUnknownType(u16); | ||
impl fmt::Display for Error { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Self::UnknownProxyType(t) => write!(f, "Unknown type of proxy: {t:#x}"), | ||
Self::XorV1Unsupported => write!(f, "XorV1 proxy types are not supported"), | ||
Self::InvalidPort(port) => write!(f, "Port {port} is not valid for remote endpoint"), | ||
Self::EmptyXorKey => write!(f, "The key material for XOR obfuscation is empty"), | ||
} | ||
} | ||
} | ||
|
||
impl std::error::Error for Error {} | ||
|
||
/// Type of a proxy configuration. Derived from the 2nd hextet of an IPv6 address in network byte | ||
/// order. E.g. an IPv6 address such as `7f7f:2323::` would have a proxy type value of `0x2323`. | ||
#[derive(PartialEq, Debug)] | ||
#[repr(u16)] | ||
enum ProxyType { | ||
Plain = 0x01, | ||
XorV1 = 0x02, | ||
XorV2 = 0x03, | ||
Plain, | ||
XorV1, | ||
XorV2, | ||
} | ||
|
||
impl TryFrom<Ipv6Addr> for ProxyType { | ||
type Error = ErrorUnknownType; | ||
|
||
/// A proxy type is represented by the second hexlet in an IPv6 address, and it is to be | ||
/// interpreted as little endian. All other data is disregarded. | ||
fn try_from(value: Ipv6Addr) -> Result<Self, Self::Error> { | ||
let mut data = Cursor::new(value.octets()); | ||
// skip the first 2 bytes since it's just padding to make the IP look more like a legit | ||
// IPv6 address. | ||
|
||
data.set_position(2); | ||
match data | ||
.read_u16::<LittleEndian>() | ||
.expect("IPv6 must have at least 16 bytes") | ||
{ | ||
impl TryFrom<[u8; 2]> for ProxyType { | ||
type Error = Error; | ||
|
||
fn try_from(bytes: [u8; 2]) -> Result<Self, Self::Error> { | ||
match u16::from_le_bytes(bytes) { | ||
0x01 => Ok(Self::Plain), | ||
0x02 => Ok(Self::XorV1), | ||
0x03 => Ok(Self::XorV2), | ||
unknown => Err(ErrorUnknownType(unknown)), | ||
unknown => Err(Error::UnknownProxyType(unknown)), | ||
} | ||
} | ||
} | ||
|
||
/// Contains valid proxy configurations as derived from a set of IPv6 addresses. | ||
pub struct AvailableProxies { | ||
/// Plain proxies just forward traffic without any obfuscation. | ||
pub plain: Vec<Plain>, | ||
/// Xor proxies xor a pre-shared key with all the traffic. | ||
pub xor: Vec<Xor>, | ||
pub trait Obfuscator: Send { | ||
/// Applies obfuscation to a given buffer of bytes. Changes the data in place. | ||
fn obfuscate(&mut self, buffer: &mut [u8]); | ||
} | ||
|
||
impl TryFrom<Vec<Ipv6Addr>> for AvailableProxies { | ||
type Error = Error; | ||
#[derive(Debug, Eq, PartialEq)] | ||
pub struct ProxyConfig { | ||
pub addr: SocketAddrV4, | ||
pub obfuscation: Option<ObfuscationConfig>, | ||
} | ||
|
||
fn try_from(ips: Vec<Ipv6Addr>) -> Result<Self, Self::Error> { | ||
let mut proxies = AvailableProxies { | ||
plain: vec![], | ||
xor: vec![], | ||
}; | ||
|
||
for ip in ips { | ||
match ProxyType::try_from(ip) { | ||
Ok(ProxyType::Plain) => { | ||
proxies | ||
.plain | ||
.push(Plain::try_from(ip).map_err(Error::InvalidPlain)?); | ||
} | ||
Ok(ProxyType::XorV2) => { | ||
proxies | ||
.xor | ||
.push(Xor::try_from(ip).map_err(Error::InvalidXor)?); | ||
} | ||
|
||
// V1 types are ignored and so are errors | ||
Ok(ProxyType::XorV1) => continue, | ||
|
||
Err(ErrorUnknownType(unknown_proxy_type)) => { | ||
log::error!("Unknown proxy type {unknown_proxy_type}"); | ||
} | ||
} | ||
} | ||
if proxies.plain.is_empty() && proxies.xor.is_empty() { | ||
return Err(Error::NoProxies); | ||
} | ||
#[derive(Debug, Eq, PartialEq)] | ||
pub enum ObfuscationConfig { | ||
XorV2(xor::XorKey), | ||
} | ||
|
||
Ok(proxies) | ||
impl ObfuscationConfig { | ||
/// Instantiate an obfuscator from the given obfuscation config. | ||
pub fn create_obfuscator(&self) -> Box<dyn Obfuscator> { | ||
match self { | ||
Self::XorV2(key) => Box::new(xor::XorObfuscator::new(*key)), | ||
} | ||
} | ||
} | ||
|
||
/// A trait that can be used by a forwarder to forward traffic. | ||
pub trait Obfuscator: Send { | ||
/// Provides the endpoint for the proxy. This address must be connected and all traffic to it | ||
/// should first be obfuscated with `Obfuscator::obfuscate`. | ||
fn addr(&self) -> SocketAddrV4; | ||
/// Applies obfuscation to a given buffer of bytes. | ||
fn obfuscate(&mut self, buffer: &mut [u8]); | ||
/// Constructs a new obfuscator of the same type and configuration, with it's internal state | ||
/// reset. | ||
fn clone(&self) -> Box<dyn Obfuscator>; | ||
impl TryFrom<Ipv6Addr> for ProxyConfig { | ||
type Error = Error; | ||
|
||
fn try_from(ip: Ipv6Addr) -> Result<Self, Self::Error> { | ||
let data = ip.octets(); | ||
|
||
let proxy_type_bytes = <[u8; 2]>::try_from(&data[2..4]).unwrap(); | ||
let proxy_config_payload = <[u8; 12]>::try_from(&data[4..16]).unwrap(); | ||
|
||
let proxy_type = ProxyType::try_from(proxy_type_bytes)?; | ||
|
||
match proxy_type { | ||
ProxyType::Plain => plain::parse_plain(proxy_config_payload), | ||
ProxyType::XorV1 => Err(Error::XorV1Unsupported), | ||
ProxyType::XorV2 => xor::parse_xor(proxy_config_payload), | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn wrong_proxy_type() { | ||
let addr: Ipv6Addr = "ffff:2345::".parse().unwrap(); | ||
match ProxyType::try_from(addr) { | ||
Err(ErrorUnknownType(0x4523)) => (), | ||
anything_else => panic!("Expected unknown type 0x33, got {anything_else:x?}"), | ||
#[cfg(test)] | ||
mod tests { | ||
use std::net::Ipv6Addr; | ||
|
||
use super::{Error, ProxyConfig}; | ||
|
||
#[test] | ||
fn wrong_proxy_type() { | ||
let addr: Ipv6Addr = "ffff:2345::".parse().unwrap(); | ||
match ProxyConfig::try_from(addr) { | ||
Err(Error::UnknownProxyType(0x4523)) => (), | ||
anything_else => panic!("Unexpected proxy config parse result: {anything_else:?}"), | ||
} | ||
} | ||
} |
Oops, something went wrong.