From 03b55b61e7e28304fc636d697511ea763fa2863e Mon Sep 17 00:00:00 2001 From: "sijie.sun" Date: Sat, 8 Mar 2025 11:58:49 +0800 Subject: [PATCH] support txt/srv record --- Cargo.lock | 404 +++++++++++++++++++++++- easytier/Cargo.toml | 3 + easytier/src/connector/dns_connector.rs | 272 ++++++++++++++++ easytier/src/connector/mod.rs | 5 + easytier/src/easytier-core.rs | 12 +- easytier/src/tunnel/mod.rs | 3 + 6 files changed, 692 insertions(+), 7 deletions(-) create mode 100644 easytier/src/connector/dns_connector.rs diff --git a/Cargo.lock b/Cargo.lock index c6b72716..413baafd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1628,6 +1628,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-encoding" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" + [[package]] name = "defguard_wireguard_rs" version = "0.4.2" @@ -1783,6 +1789,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "dlopen2" version = "0.7.0" @@ -1880,6 +1897,7 @@ dependencies = [ "gethostname 0.5.0", "git-version", "globwalk", + "hickory-resolver", "http", "http_req", "humansize", @@ -2149,6 +2167,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "enumflags2" version = "0.7.10" @@ -2975,6 +3005,51 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 1.0.3", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror 1.0.63", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror 1.0.63", + "tokio", + "tracing", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -3002,6 +3077,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "html5ever" version = "0.26.0" @@ -3192,6 +3278,124 @@ dependencies = [ "png", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3208,6 +3412,27 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "ignore" version = "0.4.22" @@ -3350,6 +3575,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -3672,12 +3909,24 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + [[package]] name = "lock_api" version = "0.4.12" @@ -3708,6 +3957,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "mac" version = "0.1.1" @@ -3753,6 +4011,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -5419,6 +5683,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-xml" version = "0.32.0" @@ -5752,6 +6022,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + [[package]] name = "ring" version = "0.17.8" @@ -7192,6 +7472,17 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sys-locale" version = "0.3.1" @@ -7816,6 +8107,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "825f6c8a18bc36d56a62f66af7296385b628c9c5543a8663d4c217fc920bfefd" +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -8485,7 +8786,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -8514,6 +8815,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -9277,6 +9590,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" @@ -9300,6 +9623,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wry" version = "0.46.1" @@ -9431,6 +9766,30 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + [[package]] name = "zbus" version = "4.4.0" @@ -9514,6 +9873,27 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -9534,6 +9914,28 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 429002b3..78ba3ab1 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -189,6 +189,9 @@ prost-reflect = { version = "0.14.5", default-features = false, features = [ # for http connector http_req = { git = "https://github.com/EasyTier/http_req.git", default-features = false, features = ["rust-tls"] } +# for dns connector +hickory-resolver = "0.24.4" + [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "freebsd"))'.dependencies] machine-uid = "0.5.3" diff --git a/easytier/src/connector/dns_connector.rs b/easytier/src/connector/dns_connector.rs new file mode 100644 index 00000000..ec5280d2 --- /dev/null +++ b/easytier/src/connector/dns_connector.rs @@ -0,0 +1,272 @@ +use std::{ + net::SocketAddr, + pin::Pin, + sync::{Arc, RwLock}, +}; + +use crate::{ + common::{error::Error, global_ctx::ArcGlobalCtx}, + tunnel::{ + Tunnel, TunnelConnector, TunnelError, ZCPacketSink, ZCPacketStream, PROTO_PORT_OFFSET, + }, +}; +use anyhow::Context; +use dashmap::DashSet; +use hickory_resolver::{ + config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}, + proto::rr::rdata::SRV, + TokioAsyncResolver, +}; +use rand::{seq::SliceRandom, Rng as _}; + +use crate::proto::common::TunnelInfo; + +use super::{create_connector_by_url, http_connector::TunnelWithInfo}; + +fn weighted_choice(options: &[(T, u64)]) -> Option<&T> { + let total_weight = options.iter().map(|(_, weight)| *weight).sum(); + let mut rng = rand::thread_rng(); + let rand_value = rng.gen_range(0..total_weight); + let mut accumulated_weight = 0; + + for (item, weight) in options { + accumulated_weight += *weight; + if rand_value < accumulated_weight { + return Some(item); + } + } + + None +} + +#[derive(Debug)] +pub struct DNSTunnelConnector { + addr: url::Url, + bind_addrs: Vec, + global_ctx: ArcGlobalCtx, + + default_resolve_config: ResolverConfig, + default_resolve_opts: ResolverOpts, +} + +impl DNSTunnelConnector { + pub fn new(addr: url::Url, global_ctx: ArcGlobalCtx) -> Self { + let mut default_resolve_config = ResolverConfig::new(); + default_resolve_config.add_name_server(NameServerConfig::new( + "223.5.5.5:53".parse().unwrap(), + Protocol::Udp, + )); + default_resolve_config.add_name_server(NameServerConfig::new( + "180.184.1.1:53".parse().unwrap(), + Protocol::Udp, + )); + Self { + addr, + bind_addrs: Vec::new(), + global_ctx, + + default_resolve_config, + default_resolve_opts: ResolverOpts::default(), + } + } + + #[tracing::instrument(ret, err)] + pub async fn handle_txt_record( + &self, + domain_name: &str, + ) -> Result, Error> { + let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or( + TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()), + ); + + let response = resolver.txt_lookup(domain_name).await.with_context(|| { + format!( + "txt_lookup failed, domain_name: {}", + domain_name.to_string() + ) + })?; + + let txt_record = response.iter().next().with_context(|| { + format!( + "no txt record found, domain_name: {}", + domain_name.to_string() + ) + })?; + + let txt_data = String::from_utf8_lossy(&txt_record.txt_data()[0]); + tracing::info!(?txt_data, ?domain_name, "get txt record"); + + let candidate_urls = txt_data + .split(" ") + .map(|s| s.to_string()) + .filter_map(|s| url::Url::parse(s.as_str()).ok()) + .collect::>(); + + // shuffle candidate_urls and get the first one + let url = candidate_urls + .choose(&mut rand::thread_rng()) + .with_context(|| { + format!( + "no valid url found, txt_data: {}, expecting an url list splitted by space", + txt_data + ) + })?; + + let connector = create_connector_by_url(url.as_str(), &self.global_ctx).await; + + connector + } + + fn handle_one_srv_record(record: &SRV, protocol: &str) -> Result<(url::Url, u64), Error> { + // port must be non-zero + if record.port() == 0 { + return Err(anyhow::anyhow!("port must be non-zero").into()); + } + + let connector_dst = record.target().to_utf8(); + let dst_url = format!("{}://{}:{}", protocol, connector_dst, record.port()); + + Ok(( + dst_url.parse().with_context(|| { + format!( + "parse dst_url failed, protocol: {}, connector_dst: {}, port: {}, dst_url: {}", + protocol, + connector_dst, + record.port(), + dst_url + ) + })?, + record.priority() as _, + )) + } + + #[tracing::instrument(ret, err)] + pub async fn handle_srv_record( + &self, + domain_name: &str, + ) -> Result, Error> { + tracing::info!("handle_srv_record: {}", domain_name); + + let resolver = TokioAsyncResolver::tokio_from_system_conf().unwrap_or( + TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()), + ); + + let srv_domains = PROTO_PORT_OFFSET + .iter() + .map(|(p, _)| (format!("_easytier._{}.{}", p, domain_name), *p)) // _easytier._udp.{domain_name} + .collect::>(); + tracing::info!("build srv_domains: {:?}", srv_domains); + let responses = Arc::new(DashSet::new()); + let srv_lookup_tasks = srv_domains + .iter() + .map(|(srv_domain, protocol)| { + let resolver = resolver.clone(); + let responses = responses.clone(); + async move { + let response = resolver.srv_lookup(srv_domain).await.with_context(|| { + format!("srv_lookup failed, srv_domain: {}", srv_domain.to_string()) + })?; + tracing::info!(?response, ?srv_domain, "srv_lookup response"); + for record in response.iter() { + let parsed_record = Self::handle_one_srv_record(record, &protocol); + tracing::info!(?parsed_record, ?srv_domain, "parsed_record"); + if parsed_record.is_err() { + eprintln!( + "got invalid srv record {:?}", + parsed_record.as_ref().unwrap_err() + ); + continue; + } + responses.insert(parsed_record.unwrap()); + } + Ok::<_, Error>(()) + } + }) + .collect::>(); + let _ = futures::future::join_all(srv_lookup_tasks).await; + + let srv_records = responses.iter().map(|r| r.clone()).collect::>(); + if srv_records.is_empty() { + return Err(anyhow::anyhow!("no srv record found").into()); + } + + let url = weighted_choice(srv_records.as_slice()).with_context(|| { + format!( + "failed to choose a srv record, domain_name: {}, srv_records: {:?}", + domain_name.to_string(), + srv_records + ) + })?; + + let connector = create_connector_by_url(url.as_str(), &self.global_ctx).await; + connector + } +} + +#[async_trait::async_trait] +impl super::TunnelConnector for DNSTunnelConnector { + async fn connect(&mut self) -> Result, TunnelError> { + let mut conn = if self.addr.scheme() == "txt" { + self.handle_txt_record(self.addr.host_str().as_ref().unwrap()) + .await + .with_context(|| "get txt record url failed")? + } else if self.addr.scheme() == "srv" { + self.handle_srv_record(self.addr.host_str().as_ref().unwrap()) + .await + .with_context(|| "get srv record url failed")? + } else { + return Err(anyhow::anyhow!( + "unsupported dns scheme: {}, expecting txt or srv", + self.addr.scheme() + ) + .into()); + }; + let t = conn.connect().await?; + let info = t.info().unwrap_or_default(); + Ok(Box::new(TunnelWithInfo::new( + t, + TunnelInfo { + local_addr: info.local_addr.clone(), + remote_addr: Some(self.addr.clone().into()), + tunnel_type: format!( + "{}-{}", + self.addr.scheme(), + info.remote_addr.unwrap_or_default() + ), + }, + ))) + } + + fn remote_url(&self) -> url::Url { + self.addr.clone() + } + + fn set_bind_addrs(&mut self, addrs: Vec) { + self.bind_addrs = addrs; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::global_ctx::{tests::get_mock_global_ctx, GlobalCtx}; + use std::sync::Arc; + + #[tokio::test] + async fn test_txt() { + let url = "txt://txt.easytier.cn"; + let global_ctx = get_mock_global_ctx(); + let mut connector = DNSTunnelConnector::new(url.parse().unwrap(), global_ctx); + let ret = connector.connect().await.unwrap(); + println!("{:?}", ret.info()); + } + + #[tokio::test] + async fn test_srv() { + let url = "srv://easytier.cn"; + let global_ctx = get_mock_global_ctx(); + let mut connector = DNSTunnelConnector::new(url.parse().unwrap(), global_ctx); + let ret = connector.connect().await.unwrap(); + println!("{:?}", ret.info()); + } +} diff --git a/easytier/src/connector/mod.rs b/easytier/src/connector/mod.rs index e92029db..f07afbc9 100644 --- a/easytier/src/connector/mod.rs +++ b/easytier/src/connector/mod.rs @@ -21,6 +21,7 @@ pub mod direct; pub mod manual; pub mod udp_hole_punch; +pub mod dns_connector; pub mod http_connector; async fn set_bind_addr_for_peer_connector( @@ -140,6 +141,10 @@ pub async fn create_connector_by_url( } return Ok(Box::new(connector)); } + "txt" | "srv" => { + let connector = dns_connector::DNSTunnelConnector::new(url, global_ctx.clone()); + return Ok(Box::new(connector)); + } _ => { return Err(Error::InvalidUrl(url.into())); } diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index 5e32cb2a..5ccdd4fd 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -24,12 +24,13 @@ use easytier::{ scoped_task::ScopedTask, stun::MockStunInfoCollector, }, - connector::create_connector_by_url, + connector::{create_connector_by_url, dns_connector::DNSTunnelConnector}, launcher, proto::{ self, common::{CompressionAlgoPb, NatType}, }, + tunnel::PROTO_PORT_OFFSET, utils::{init_logger, setup_panic_handler}, web_client, }; @@ -338,8 +339,6 @@ rust_i18n::i18n!("locales", fallback = "en"); impl Cli { fn parse_listeners(no_listener: bool, listeners: Vec) -> anyhow::Result> { - let proto_port_offset = vec![("tcp", 0), ("udp", 0), ("wg", 1), ("ws", 1), ("wss", 2)]; - if no_listener || listeners.is_empty() { return Ok(vec![]); } @@ -348,8 +347,8 @@ impl Cli { let mut listeners: Vec = Vec::new(); if origin_listners.len() == 1 { if let Ok(port) = origin_listners[0].parse::() { - for (proto, offset) in proto_port_offset { - listeners.push(format!("{}://0.0.0.0:{}", proto, port + offset)); + for (proto, offset) in PROTO_PORT_OFFSET { + listeners.push(format!("{}://0.0.0.0:{}", proto, port + *offset)); } return Ok(listeners); } @@ -364,7 +363,7 @@ impl Cli { panic!("failed to parse listener: {}", l); } } else { - let Some((proto, offset)) = proto_port_offset + let Some((proto, offset)) = PROTO_PORT_OFFSET .iter() .find(|(proto, _)| *proto == proto_port[0]) else { @@ -875,6 +874,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> { token.to_string(), ); tokio::signal::ctrl_c().await.unwrap(); + DNSTunnelConnector::new("".parse().unwrap(), global_ctx); return Ok(()); } diff --git a/easytier/src/tunnel/mod.rs b/easytier/src/tunnel/mod.rs index 615feff3..b3deb4ee 100644 --- a/easytier/src/tunnel/mod.rs +++ b/easytier/src/tunnel/mod.rs @@ -22,6 +22,9 @@ pub mod stats; pub mod tcp; pub mod udp; +pub const PROTO_PORT_OFFSET: &[(&str, u16)] = + &[("tcp", 0), ("udp", 0), ("wg", 1), ("ws", 1), ("wss", 2)]; + #[cfg(feature = "wireguard")] pub mod wireguard;