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

Feat: implement tcp/udp bootstrap resolver with arbitrary port other than UDP over 53 #52

Merged
merged 4 commits into from
Jan 23, 2024
Merged
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@
You should also include the user name that made the change.
-->

## 0.3.2

### Improvements

- Feat: Support TCP and UDP bootstrap DNS protocols. In addition to the existing format like "1.1.1.1" that means "udp://1.1.1.1:53", formats like "tcp://1.1.1.1:53" (TCP support) "udp://1.2.3.4:50053" (Non standard port) works. See "README.md" and "doh-auth-proxy.toml" for configuration for the detail.
- Refactor: Lots of minor improvements

## 0.3.1

### Bugfix

- Fix several error handlers

## 0.3.0

### Improvements
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Jun Kurihara
Copyright (c) 2024 Jun Kurihara

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ github.com. 60 IN A 52.69.186.44
~~~~~~~
```

The parameter `bootstrap-dns` is used to resolve the IP address of the host of `target-url` (i.e., target DoH server).
The parameter `bootstrap_dns` is used to resolve the IP address of the host of `target_urls` (i.e., target DoH server). The `bootstrap_dns` allows non-standard DNS ports other than `53` and TCP queries, which can be specified as an url-like format, e.g., `tcp://1.1.1.1`, `tcp://127.0.0.1:12345`, `127.0.0.1:50053`, where UDP and port `53` are used if omitted.

If you run without `--config` option, i.e., simply hit `$ ./doh-auth-proxy`, the followings are applied as default parameters:

Expand Down
6 changes: 4 additions & 2 deletions doh-auth-proxy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
## Address to listen to.
listen_addresses = ['127.0.0.1:50053', '[::1]:50053']

## DNS (Do53) resolver addresses for bootstrap
bootstrap_dns = ["8.8.8.8", "1.1.1.1"]
## DNS (Do53) resolver addresses for bootstrap.
## You can omit protocol name and port number, default is udp over port 53.
## The first one is used for bootstrap, and the rest are used for fallback as ordered.
bootstrap_dns = ["udp://8.8.8.8:53", "1.1.1.1:53", "8.8.4.4", "tcp://1.0.0.1"]

## Minutes to re-resolve the IP addr of the nexthop and authentication endpoint url
## Ip addresses are first resolved by bootstrap DNS, after that, they will be resolved by (MO)DoH resolver itself.
Expand Down
6 changes: 3 additions & 3 deletions proxy-bin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "doh-auth-proxy"
description = "DNS Proxy for DoH, ODoH and Mutualized ODoH with Authorization"
version = "0.3.1"
version = "0.3.2"
authors = ["Jun Kurihara"]
homepage = "https://github.com/junkurihara/doh-auth-proxy"
repository = "https://github.com/junkurihara/doh-auth-proxy"
Expand Down Expand Up @@ -37,7 +37,7 @@ doh-auth-proxy-lib = { path = "../proxy-lib/" }
anyhow = "1.0.79"
mimalloc = { version = "*", default-features = false }
serde = { version = "1.0.195", default-features = false, features = ["derive"] }
derive_builder = "0.12.0"
derive_builder = "0.13.0"
tokio = { version = "1.35.1", default-features = false, features = [
"net",
"rt-multi-thread",
Expand All @@ -48,7 +48,7 @@ tokio = { version = "1.35.1", default-features = false, features = [
async-trait = "0.1.77"

# config
clap = { version = "4.4.14", features = ["std", "cargo", "wrap_help"] }
clap = { version = "4.4.18", features = ["std", "cargo", "wrap_help"] }
toml = { version = "0.8.8", default-features = false, features = ["parse"] }
hot_reload = "0.1.4"

Expand Down
1 change: 1 addition & 0 deletions proxy-bin/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod parse;
mod plugins;
mod target_config;
mod toml;
mod utils_dns_proto;
mod utils_verifier;

pub use {
Expand Down
13 changes: 9 additions & 4 deletions proxy-bin/src/config/target_config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{toml::ConfigToml, utils_verifier::*};
use super::{toml::ConfigToml, utils_dns_proto::parse_proto_sockaddr_str, utils_verifier::*};
use crate::{constants::*, error::*, log::*};
use async_trait::async_trait;
use doh_auth_proxy_lib::{
Expand Down Expand Up @@ -76,12 +76,17 @@ impl TryInto<ProxyConfig> for &TargetConfig {
/////////////////////////////
// bootstrap dns
if let Some(val) = &self.config_toml.bootstrap_dns {
if !val.iter().all(|v| verify_ip_addr(v).is_ok()) {
let vec_proto_sockaddr = val.iter().map(parse_proto_sockaddr_str).collect::<Vec<_>>();
if vec_proto_sockaddr.iter().any(|x| x.is_err()) {
bail!("Invalid bootstrap DNS address");
}
proxy_config.bootstrap_dns.ips = val.iter().map(|x| x.parse().unwrap()).collect()
proxy_config.bootstrap_dns = vec_proto_sockaddr
.iter()
.map(|x| x.as_ref().unwrap().clone())
.collect::<Vec<_>>()
.try_into()?;
};
info!("Bootstrap DNS: {:?}", proxy_config.bootstrap_dns.ips);
info!("Bootstrap DNS: {}", proxy_config.bootstrap_dns);

/////////////////////////////
// endpoint re-resolution period
Expand Down
95 changes: 95 additions & 0 deletions proxy-bin/src/config/utils_dns_proto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};

const PREFIX_UDP: &str = "udp://";
const PREFIX_TCP: &str = "tcp://";
const DEFAULT_DNS_PORT: u16 = 53;

/// Parse as string in the form of "<proto>://<ip_addr>:<port>", where "<proto>://" can be omitted and then it will be treated as "udp://".
/// ":<port>" can also be omitted and then it will be treated as ":53".
/// - <proto>: "udp" or "tcp"
/// - <ip_addr>: IPv4 or IPv6 address, where IPv6 address must be enclosed in square brackets like "[::1]"
/// - <port>: port number, which must be explicitly specified.
pub(crate) fn parse_proto_sockaddr_str<T: AsRef<str>>(val: T) -> anyhow::Result<(String, SocketAddr)> {
let val = val.as_ref();

// parse proto
let (proto, val_rest) = if val.starts_with(PREFIX_UDP) {
("udp", val.strip_prefix(PREFIX_UDP).unwrap())
} else if val.starts_with(PREFIX_TCP) {
("tcp", val.strip_prefix(PREFIX_TCP).unwrap())
} else {
("udp", val)
};

// parse socket address
let socket_addr = if val.contains('[') && val.contains(']') {
// ipv6
let mut split = val_rest.strip_prefix('[').unwrap().split(']').filter(|s| !s.is_empty());
let ip_part = split
.next()
.ok_or(anyhow::anyhow!("Invalid IPv6 address specified"))?
.parse::<Ipv6Addr>()?;
let port_part = if let Some(port_part) = split.next() {
anyhow::ensure!(port_part.starts_with(':'), "Invalid port number specified");
port_part.strip_prefix(':').unwrap().parse::<u16>()?
} else {
DEFAULT_DNS_PORT
};
SocketAddr::new(IpAddr::V6(ip_part), port_part)
} else {
// ipv4
let mut split = val_rest.split(':').filter(|s| !s.is_empty());
let ip_part = split
.next()
.ok_or(anyhow::anyhow!("Invalid IPv4 address specified"))?
.parse::<Ipv4Addr>()?;
let port_part = if let Some(port_part) = split.next() {
port_part.parse::<u16>()?
} else {
DEFAULT_DNS_PORT
};
SocketAddr::new(IpAddr::V4(ip_part), port_part)
};

Ok((proto.to_owned(), socket_addr))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_proto_sockaddr_str() {
let (proto, socket_addr) = parse_proto_sockaddr_str("tcp://[::1]:50053").unwrap();
assert_eq!(proto, "tcp");
assert_eq!(socket_addr, SocketAddr::from((Ipv6Addr::LOCALHOST, 50053)));

let (proto, socket_addr) = parse_proto_sockaddr_str("tcp://[::1]").unwrap();
assert_eq!(proto, "tcp");
assert_eq!(socket_addr, SocketAddr::from((Ipv6Addr::LOCALHOST, 53)));

let (proto, socket_addr) = parse_proto_sockaddr_str("[::1]:50053").unwrap();
assert_eq!(proto, "udp");
assert_eq!(socket_addr, SocketAddr::from((Ipv6Addr::LOCALHOST, 50053)));

let (proto, socket_addr) = parse_proto_sockaddr_str("[::1]").unwrap();
assert_eq!(proto, "udp");
assert_eq!(socket_addr, SocketAddr::from((Ipv6Addr::LOCALHOST, 53)));

let (proto, socket_addr) = parse_proto_sockaddr_str("udp://8.8.8.8:50053").unwrap();
assert_eq!(proto, "udp");
assert_eq!(socket_addr, SocketAddr::from(([8, 8, 8, 8], 50053)));

let (proto, socket_addr) = parse_proto_sockaddr_str("udp://8.8.8.8").unwrap();
assert_eq!(proto, "udp");
assert_eq!(socket_addr, SocketAddr::from(([8, 8, 8, 8], 53)));

let (proto, socket_addr) = parse_proto_sockaddr_str("8.8.8.8:50053").unwrap();
assert_eq!(proto, "udp");
assert_eq!(socket_addr, SocketAddr::from(([8, 8, 8, 8], 50053)));

let (proto, socket_addr) = parse_proto_sockaddr_str("8.8.8.8").unwrap();
assert_eq!(proto, "udp");
assert_eq!(socket_addr, SocketAddr::from(([8, 8, 8, 8], 53)));
}
}
1 change: 1 addition & 0 deletions proxy-bin/src/config/utils_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub(crate) fn verify_sock_addr(arg_val: &str) -> Result<(), String> {
}
}

#[allow(dead_code)]
pub(crate) fn verify_ip_addr(arg_val: &str) -> Result<(), String> {
match arg_val.parse::<IpAddr>() {
Ok(_addr) => Ok(()),
Expand Down
8 changes: 4 additions & 4 deletions proxy-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "doh-auth-proxy-lib"
description = "DNS Proxy Library for DoH, ODoH and Mutualized ODoH with Authorization"
version = "0.3.1"
version = "0.3.2"
authors = ["Jun Kurihara"]
homepage = "https://github.com/junkurihara/doh-auth-proxy"
repository = "https://github.com/junkurihara/doh-auth-proxy"
Expand Down Expand Up @@ -59,7 +59,7 @@ hickory-proto = { version = "0.24.0", default-features = false }
data-encoding = "2.5.0"
hashlink = "0.9.0"
cedarwood = "0.4.6"
regex = "1.10.2"
regex = "1.10.3"

# network
socket2 = "0.5.5"
Expand All @@ -74,8 +74,8 @@ reqwest = { version = "0.11.23", default-features = false, features = [
url = "2.5.0"

# for bootstrap dns resolver
hickory-resolver = { version = "0.24.0", default-features = false, features = [
"tokio-runtime",
hickory-client = { version = "0.24.0", default-features = false, features = [
"dnssec",
] }

# authentication
Expand Down
Loading