From 520b9dd3b255641a4eb09e561cf94426e315f0f5 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Tue, 1 Sep 2020 12:46:55 +0200 Subject: [PATCH 1/9] feat: implement /resolve Signed-off-by: ljedrz --- conformance/test/index.js | 11 +++++++++- http/src/v0.rs | 2 ++ http/src/v0/ipns.rs | 45 +++++++++++++++++++++++++++++++++++++++ src/ipns/dns.rs | 2 +- src/lib.rs | 4 ++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 http/src/v0/ipns.rs diff --git a/conformance/test/index.js b/conformance/test/index.js index 2fcafd953..22bd5ba9f 100644 --- a/conformance/test/index.js +++ b/conformance/test/index.js @@ -34,7 +34,16 @@ const factory = createFactory(options) // tests.miscellaneous(factory, { skip: [ 'dns', - 'resolve', + // the cidBase param is not implemented yet + 'should resolve an IPFS hash and return a base64url encoded CID in path', + // different Cid, the /path/to/testfile.txt suffix shouldn't be there + 'should resolve an IPFS path link', + // different Cid, missing "/path/to" in the middle + 'should resolve up to the last node across multiple nodes', + // expected "true", got "false" + 'should resolve an IPNS DNS link', + // HTTP: not implemented + 'should resolve IPNS link recursively', // these cause a hang 20% of time: 'should respect timeout option when getting the node id', 'should respect timeout option when getting the node version', diff --git a/http/src/v0.rs b/http/src/v0.rs index 4d4c8a7aa..ce3e55ea5 100644 --- a/http/src/v0.rs +++ b/http/src/v0.rs @@ -6,6 +6,7 @@ pub mod block; pub mod dag; pub mod dht; pub mod id; +pub mod ipns; pub mod pin; pub mod pubsub; pub mod refs; @@ -90,6 +91,7 @@ pub fn routes( and_boxed!(warp::path!("get"), root_files::get(ipfs)), and_boxed!(warp::path!("refs" / "local"), refs::local(ipfs)), and_boxed!(warp::path!("refs"), refs::refs(ipfs)), + and_boxed!(warp::path!("resolve"), ipns::resolve(ipfs)), warp::path!("version") .and(query::()) .and_then(version::version), diff --git a/http/src/v0/ipns.rs b/http/src/v0/ipns.rs new file mode 100644 index 000000000..5f9ec3581 --- /dev/null +++ b/http/src/v0/ipns.rs @@ -0,0 +1,45 @@ +use crate::v0::support::{with_ipfs, StringError, StringSerialized}; +use ipfs::{Ipfs, IpfsPath, IpfsTypes}; +use serde::{Deserialize, Serialize}; +use warp::{query, Filter, Rejection, Reply}; + +#[derive(Debug, Deserialize)] +pub struct ResolveQuery { + // the name to resolve + arg: StringSerialized, + #[serde(rename = "dht-record-count")] + dht_record_count: Option, + #[serde(rename = "dht-timeout")] + dht_timeout: Option, +} + +pub fn resolve( + ipfs: &Ipfs, +) -> impl Filter + Clone { + with_ipfs(ipfs) + .and(query::()) + .and_then(resolve_query) +} + +async fn resolve_query( + ipfs: Ipfs, + query: ResolveQuery, +) -> Result { + let ResolveQuery { arg, .. } = query; + let name = arg.into_inner(); + let path = ipfs + .resolve(&name) + .await + .map_err(StringError::from)? + .to_string(); + + let response = ResolveResponse { path }; + + Ok(warp::reply::json(&response)) +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +struct ResolveResponse { + path: String, +} diff --git a/src/ipns/dns.rs b/src/ipns/dns.rs index 58bb03f76..ffeb4c31c 100644 --- a/src/ipns/dns.rs +++ b/src/ipns/dns.rs @@ -18,7 +18,7 @@ use thiserror::Error; #[error("no dnslink entry")] pub struct DnsLinkError; -type FutureAnswer = Pin>>>; +type FutureAnswer = Pin> + Send>>; pub struct DnsLinkFuture { query: SelectOk, diff --git a/src/lib.rs b/src/lib.rs index fcf00a6c8..d1c034c95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -859,6 +859,10 @@ impl Ipfs { refs::iplds_refs(self, iplds, max_depth, unique) } + pub async fn resolve(&self, path: &IpfsPath) -> Result { + self.ipns().resolve(path).await + } + /// Exit daemon. pub async fn exit_daemon(self) { // FIXME: this is a stopgap measure needed while repo is part of the struct Ipfs instead of From 1c9ac1ab58c894f3424a706933290444c9d58950 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Tue, 1 Sep 2020 13:50:16 +0200 Subject: [PATCH 2/9] feat: implement /dns Signed-off-by: ljedrz --- conformance/test/index.js | 3 ++- http/src/v0.rs | 1 + http/src/v0/ipns.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/conformance/test/index.js b/conformance/test/index.js index 22bd5ba9f..508a24d63 100644 --- a/conformance/test/index.js +++ b/conformance/test/index.js @@ -33,7 +33,8 @@ const factory = createFactory(options) // Phase 1.0-ish // tests.miscellaneous(factory, { skip: [ - 'dns', + // recursive resolving is not implemented yet + 'should recursively resolve ipfs.io', // the cidBase param is not implemented yet 'should resolve an IPFS hash and return a base64url encoded CID in path', // different Cid, the /path/to/testfile.txt suffix shouldn't be there diff --git a/http/src/v0.rs b/http/src/v0.rs index ce3e55ea5..b6f065909 100644 --- a/http/src/v0.rs +++ b/http/src/v0.rs @@ -88,6 +88,7 @@ pub fn routes( and_boxed!(warp::path!("id"), id::identity(ipfs)), and_boxed!(warp::path!("add"), root_files::add(ipfs)), and_boxed!(warp::path!("cat"), root_files::cat(ipfs)), + and_boxed!(warp::path!("dns"), ipns::dns(ipfs)), and_boxed!(warp::path!("get"), root_files::get(ipfs)), and_boxed!(warp::path!("refs" / "local"), refs::local(ipfs)), and_boxed!(warp::path!("refs"), refs::refs(ipfs)), diff --git a/http/src/v0/ipns.rs b/http/src/v0/ipns.rs index 5f9ec3581..bc2fdf6bf 100644 --- a/http/src/v0/ipns.rs +++ b/http/src/v0/ipns.rs @@ -43,3 +43,34 @@ async fn resolve_query( struct ResolveResponse { path: String, } + +#[derive(Debug, Deserialize)] +pub struct DnsQuery { + // the name to resolve + arg: StringSerialized, +} + +pub fn dns( + ipfs: &Ipfs, +) -> impl Filter + Clone { + with_ipfs(ipfs).and(query::()).and_then(dns_query) +} + +async fn dns_query(ipfs: Ipfs, query: DnsQuery) -> Result { + let DnsQuery { arg, .. } = query; + let path = ipfs + .resolve(&arg.into_inner()) + .await + .map_err(StringError::from)? + .to_string(); + + let response = DnsResponse { path }; + + Ok(warp::reply::json(&response)) +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +struct DnsResponse { + path: String, +} From 8aee3596251c93dc51468d33c0a5ab337f261457 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Tue, 1 Sep 2020 14:59:19 +0200 Subject: [PATCH 3/9] fix: enable domain names not starting with /ipns/ to be resolved with /dns Signed-off-by: ljedrz --- http/src/v0/ipns.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/http/src/v0/ipns.rs b/http/src/v0/ipns.rs index bc2fdf6bf..8a8be9bb6 100644 --- a/http/src/v0/ipns.rs +++ b/http/src/v0/ipns.rs @@ -47,7 +47,7 @@ struct ResolveResponse { #[derive(Debug, Deserialize)] pub struct DnsQuery { // the name to resolve - arg: StringSerialized, + arg: String, } pub fn dns( @@ -58,8 +58,20 @@ pub fn dns( async fn dns_query(ipfs: Ipfs, query: DnsQuery) -> Result { let DnsQuery { arg, .. } = query; + // attempt to parse the argument prepended with "/ipns/" if it fails to parse like a compliant + // IpfsPath and there is no leading slash + let path = if !arg.starts_with('/') { + if let Ok(parsed) = arg.parse() { + Ok(parsed) + } else { + format!("/ipns/{}", arg).parse() + } + } else { + arg.parse() + } + .map_err(StringError::from)?; let path = ipfs - .resolve(&arg.into_inner()) + .resolve(&path) .await .map_err(StringError::from)? .to_string(); From ce88339334de0b3d43e22d8434e2e0d24068b283 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Tue, 1 Sep 2020 16:23:43 +0200 Subject: [PATCH 4/9] feat: enable recursive /dns Signed-off-by: ljedrz --- conformance/test/index.js | 2 -- http/src/v0/ipns.rs | 8 +++++--- src/lib.rs | 22 ++++++++++++++++++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/conformance/test/index.js b/conformance/test/index.js index 508a24d63..b0aaeacfe 100644 --- a/conformance/test/index.js +++ b/conformance/test/index.js @@ -33,8 +33,6 @@ const factory = createFactory(options) // Phase 1.0-ish // tests.miscellaneous(factory, { skip: [ - // recursive resolving is not implemented yet - 'should recursively resolve ipfs.io', // the cidBase param is not implemented yet 'should resolve an IPFS hash and return a base64url encoded CID in path', // different Cid, the /path/to/testfile.txt suffix shouldn't be there diff --git a/http/src/v0/ipns.rs b/http/src/v0/ipns.rs index 8a8be9bb6..24a58f579 100644 --- a/http/src/v0/ipns.rs +++ b/http/src/v0/ipns.rs @@ -28,7 +28,7 @@ async fn resolve_query( let ResolveQuery { arg, .. } = query; let name = arg.into_inner(); let path = ipfs - .resolve(&name) + .resolve(&name, false) .await .map_err(StringError::from)? .to_string(); @@ -48,6 +48,7 @@ struct ResolveResponse { pub struct DnsQuery { // the name to resolve arg: String, + recursive: Option, } pub fn dns( @@ -57,7 +58,7 @@ pub fn dns( } async fn dns_query(ipfs: Ipfs, query: DnsQuery) -> Result { - let DnsQuery { arg, .. } = query; + let DnsQuery { arg, recursive } = query; // attempt to parse the argument prepended with "/ipns/" if it fails to parse like a compliant // IpfsPath and there is no leading slash let path = if !arg.starts_with('/') { @@ -70,8 +71,9 @@ async fn dns_query(ipfs: Ipfs, query: DnsQuery) -> Result Ipfs { refs::iplds_refs(self, iplds, max_depth, unique) } - pub async fn resolve(&self, path: &IpfsPath) -> Result { - self.ipns().resolve(path).await + pub async fn resolve(&self, path: &IpfsPath, recursive: bool) -> Result { + let ipns = self.ipns(); + let mut resolved = ipns.resolve(path).await; + + if recursive { + let mut previous = None; + while let Ok(ref res) = resolved { + if let Some(ref prev) = previous { + if prev == res { + break; + } + } + previous = Some(res.clone()); + resolved = ipns.resolve(&res).await; + } + + resolved + } else { + resolved + } } /// Exit daemon. From a29f3d328c9ef184db02be774c082d6388b02cd3 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Wed, 2 Sep 2020 10:07:14 +0200 Subject: [PATCH 5/9] refactor: reuse an existing resolve function Signed-off-by: ljedrz --- examples/ipfs_ipns_test.rs | 6 ++--- http/src/v0/ipns.rs | 4 ++-- src/lib.rs | 47 ++++++++++++++++---------------------- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/examples/ipfs_ipns_test.rs b/examples/ipfs_ipns_test.rs index b78007c6a..d43ddae96 100644 --- a/examples/ipfs_ipns_test.rs +++ b/examples/ipfs_ipns_test.rs @@ -21,15 +21,15 @@ async fn main() { .unwrap(); // Resolve a Block - let new_ipfs_path = ipfs.resolve_ipns(&ipns_path).await.unwrap(); + let new_ipfs_path = ipfs.resolve_ipns(&ipns_path, false).await.unwrap(); assert_eq!(ipfs_path, new_ipfs_path); // Resolve dnslink let ipfs_path = IpfsPath::from_str("/ipns/ipfs.io").unwrap(); println!("Resolving {:?}", ipfs_path.to_string()); - let ipfs_path = ipfs.resolve_ipns(&ipfs_path).await.unwrap(); + let ipfs_path = ipfs.resolve_ipns(&ipfs_path, false).await.unwrap(); println!("Resolved stage 1: {:?}", ipfs_path.to_string()); - let ipfs_path = ipfs.resolve_ipns(&ipfs_path).await.unwrap(); + let ipfs_path = ipfs.resolve_ipns(&ipfs_path, false).await.unwrap(); println!("Resolved stage 2: {:?}", ipfs_path.to_string()); ipfs.exit_daemon().await; diff --git a/http/src/v0/ipns.rs b/http/src/v0/ipns.rs index 24a58f579..2fe7ad2e4 100644 --- a/http/src/v0/ipns.rs +++ b/http/src/v0/ipns.rs @@ -28,7 +28,7 @@ async fn resolve_query( let ResolveQuery { arg, .. } = query; let name = arg.into_inner(); let path = ipfs - .resolve(&name, false) + .resolve_ipns(&name, false) .await .map_err(StringError::from)? .to_string(); @@ -73,7 +73,7 @@ async fn dns_query(ipfs: Ipfs, query: DnsQuery) -> Result Ipfs { } /// Resolves a ipns path to an ipld path. - pub async fn resolve_ipns(&self, path: &IpfsPath) -> Result { - self.ipns() - .resolve(path) - .instrument(self.span.clone()) - .await + pub async fn resolve_ipns(&self, path: &IpfsPath, recursive: bool) -> Result { + let ipns = self.ipns(); + let mut resolved = ipns.resolve(path).await; + + if recursive { + let mut previous = None; + while let Ok(ref res) = resolved { + if let Some(ref prev) = previous { + if prev == res { + break; + } + } + previous = Some(res.clone()); + resolved = ipns.resolve(&res).await; + } + + resolved + } else { + resolved + } } /// Publishes an ipld path. @@ -859,28 +874,6 @@ impl Ipfs { refs::iplds_refs(self, iplds, max_depth, unique) } - pub async fn resolve(&self, path: &IpfsPath, recursive: bool) -> Result { - let ipns = self.ipns(); - let mut resolved = ipns.resolve(path).await; - - if recursive { - let mut previous = None; - while let Ok(ref res) = resolved { - if let Some(ref prev) = previous { - if prev == res { - break; - } - } - previous = Some(res.clone()); - resolved = ipns.resolve(&res).await; - } - - resolved - } else { - resolved - } - } - /// Exit daemon. pub async fn exit_daemon(self) { // FIXME: this is a stopgap measure needed while repo is part of the struct Ipfs instead of From 9d76f13620dc4899c814c78a9b61db5c2b0ab08a Mon Sep 17 00:00:00 2001 From: ljedrz Date: Wed, 2 Sep 2020 11:39:27 +0200 Subject: [PATCH 6/9] temp: preserve the DNS error values Signed-off-by: ljedrz --- src/ipns/dns.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/ipns/dns.rs b/src/ipns/dns.rs index ffeb4c31c..4d0f75dc8 100644 --- a/src/ipns/dns.rs +++ b/src/ipns/dns.rs @@ -8,15 +8,21 @@ use domain_resolv::{stub::Answer, StubResolver}; use futures::future::{select_ok, SelectOk}; use futures::pin_mut; use std::future::Future; -use std::io; use std::pin::Pin; use std::str::FromStr; use std::task::{Context, Poll}; -use thiserror::Error; +use std::{fmt, io}; -#[derive(Debug, Error)] -#[error("no dnslink entry")] -pub struct DnsLinkError; +#[derive(Debug)] +pub struct DnsLinkError(String); + +impl fmt::Display for DnsLinkError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "DNS error: {}", self.0) + } +} + +impl std::error::Error for DnsLinkError {} type FutureAnswer = Pin> + Send>>; @@ -46,11 +52,13 @@ impl Future for DnsLinkFuture { if !rest.is_empty() { _self.query = select_ok(rest); } else { - return Poll::Ready(Err(DnsLinkError.into())); + return Poll::Ready(Err( + DnsLinkError("no DNS records found".to_owned()).into() + )); } } Poll::Pending => return Poll::Pending, - Poll::Ready(Err(_)) => return Poll::Ready(Err(DnsLinkError.into())), + Poll::Ready(Err(e)) => return Poll::Ready(Err(DnsLinkError(e.to_string()).into())), } } } From bef69b171e11b1ce1ec30118f3fbacde58190ce6 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Wed, 2 Sep 2020 12:12:11 +0200 Subject: [PATCH 7/9] fix: remove the panics from dag resolution methods Signed-off-by: ljedrz --- src/dag.rs | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/dag.rs b/src/dag.rs index cb0db59dd..647f93995 100644 --- a/src/dag.rs +++ b/src/dag.rs @@ -46,6 +46,14 @@ pub enum ResolveError { /// Path attempted to resolve through a property, index or link which did not exist. #[error("no link named {:?} under {0}", .1.iter().last().unwrap())] NotFound(Cid, SlashedPath), + + /// Tried to use a path neiter containing nor resolving to a Cid. + #[error("the path neiter contains nor resolves to a Cid")] + NoCid(IpfsPath), + + /// Couldn't resolve a path via IPNS. + #[error("can't resolve an IPNS path")] + IpnsResolutionFailed(IpfsPath), } #[derive(Debug, Error)] @@ -178,19 +186,24 @@ impl IpldDag { /// /// Returns the resolved node as `Ipld`. pub async fn get(&self, path: IpfsPath) -> Result { - // FIXME: do ipns resolve first - let cid = match path.root().cid() { + let resolved_path = self + .ipfs + .resolve_ipns(&path, true) + .await + .map_err(|_| ResolveError::IpnsResolutionFailed(path))?; + + let cid = match resolved_path.root().cid() { Some(cid) => cid, - None => panic!("Ipns resolution not implemented; expected a Cid-based path"), + None => return Err(ResolveError::NoCid(resolved_path)), }; - let mut iter = path.iter().peekable(); + let mut iter = resolved_path.iter().peekable(); let (node, _) = match self.resolve0(cid, &mut iter, true).await { Ok(t) => t, Err(e) => { drop(iter); - return Err(e.with_path(path)); + return Err(e.with_path(resolved_path)); } }; @@ -213,19 +226,24 @@ impl IpldDag { path: IpfsPath, follow_links: bool, ) -> Result<(ResolvedNode, SlashedPath), ResolveError> { - // FIXME: do ipns resolve first - let cid = match path.root().cid() { + let resolved_path = self + .ipfs + .resolve_ipns(&path, true) + .await + .map_err(|_| ResolveError::IpnsResolutionFailed(path))?; + + let cid = match resolved_path.root().cid() { Some(cid) => cid, - None => panic!("Ipns resolution not implemented; expected a Cid-based path"), + None => return Err(ResolveError::NoCid(resolved_path)), }; let (node, matched_segments) = { - let mut iter = path.iter().peekable(); + let mut iter = resolved_path.iter().peekable(); match self.resolve0(cid, &mut iter, follow_links).await { Ok(t) => t, Err(e) => { drop(iter); - return Err(e.with_path(path)); + return Err(e.with_path(resolved_path)); } } }; @@ -233,7 +251,7 @@ impl IpldDag { // we only care about returning this remaining_path with segments up until the last // document but it can and should contain all of the following segments (if any). there // could be more segments when `!follow_links`. - let remaining_path = path.into_shifted(matched_segments); + let remaining_path = resolved_path.into_shifted(matched_segments); Ok((node, remaining_path)) } From 6825c4e2bb4d41861fd98bc7c09a65f8271f4463 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Mon, 7 Sep 2020 11:13:38 +0200 Subject: [PATCH 8/9] fix: use a HashSet to avoid loops when resolving recursively Signed-off-by: ljedrz --- src/lib.rs | 31 ++++++++++++++++--------------- src/path.rs | 6 +++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 49b56a78d..ee4d98b84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ use tracing::Span; use tracing_futures::Instrument; use std::borrow::Borrow; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt; use std::future::Future; use std::ops::Range; @@ -447,25 +447,26 @@ impl Ipfs { /// Resolves a ipns path to an ipld path. pub async fn resolve_ipns(&self, path: &IpfsPath, recursive: bool) -> Result { - let ipns = self.ipns(); - let mut resolved = ipns.resolve(path).await; - - if recursive { - let mut previous = None; - while let Ok(ref res) = resolved { - if let Some(ref prev) = previous { - if prev == res { + async move { + let ipns = self.ipns(); + let mut resolved = ipns.resolve(path).await; + + if recursive { + let mut seen = HashSet::with_capacity(1); + while let Ok(ref res) = resolved { + if !seen.insert(res.clone()) { break; } + resolved = ipns.resolve(&res).await; } - previous = Some(res.clone()); - resolved = ipns.resolve(&res).await; - } - resolved - } else { - resolved + resolved + } else { + resolved + } } + .instrument(self.span.clone()) + .await } /// Publishes an ipld path. diff --git a/src/path.rs b/src/path.rs index f6d5c316e..40db9446c 100644 --- a/src/path.rs +++ b/src/path.rs @@ -10,7 +10,7 @@ use thiserror::Error; // TODO: it might be useful to split this into CidPath and IpnsPath, then have Ipns resolve through // latter into CidPath (recursively) and have dag.rs support only CidPath. Keep IpfsPath as a // common abstraction which can be either. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct IpfsPath { root: PathRoot, pub(crate) path: SlashedPath, @@ -161,7 +161,7 @@ impl TryInto for IpfsPath { /// /// UTF-8 originates likely from UnixFS related protobuf descriptions, where dag-pb links have /// UTF-8 names, which equal to SlashedPath segments. -#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Default, Hash)] pub struct SlashedPath { path: Vec, } @@ -241,7 +241,7 @@ impl<'a> PartialEq<[&'a str]> for SlashedPath { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum PathRoot { Ipld(Cid), Ipns(PeerId), From 3174225d7c04e21f36bca2229dff462b07908541 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Tue, 8 Sep 2020 15:21:14 +0200 Subject: [PATCH 9/9] fix: make DNS resolution work in Windows Signed-off-by: ljedrz --- Cargo.lock | 28 ++++++++++++++++++++++++++++ Cargo.toml | 4 ++++ src/ipns/dns.rs | 41 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ad0dce60..0cef06f50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1166,6 +1166,18 @@ dependencies = [ "libc", ] +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi 0.3.9", + "winreg", +] + [[package]] name = "ipfs" version = "0.1.0" @@ -1184,6 +1196,7 @@ dependencies = [ "either", "futures", "hex-literal", + "ipconfig", "ipfs-unixfs", "libp2p", "multibase", @@ -3184,6 +3197,12 @@ dependencies = [ "libc", ] +[[package]] +name = "widestring" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c" + [[package]] name = "winapi" version = "0.2.8" @@ -3227,6 +3246,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 0cb8dae01..445bc162a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,10 @@ tracing = { default-features = false, features = ["log"], version = "0.1" } tracing-futures = { default-features = false, features = ["std", "futures-03"], version = "0.2" } void = { default-features = false, version = "1.0" } +[target.'cfg(windows)'.dependencies] +# required for DNS resolution +ipconfig = { default-features = false, version = "0.2" } + [build-dependencies] prost-build = { default-features = false, version = "0.6" } diff --git a/src/ipns/dns.rs b/src/ipns/dns.rs index 4d0f75dc8..a5c9e1a10 100644 --- a/src/ipns/dns.rs +++ b/src/ipns/dns.rs @@ -64,18 +64,51 @@ impl Future for DnsLinkFuture { } } +#[cfg(not(target_os = "windows"))] +fn create_resolver() -> Result { + Ok(StubResolver::default()) +} + +#[cfg(target_os = "windows")] +fn create_resolver() -> Result { + use domain_resolv::stub::conf::ResolvConf; + use std::{collections::HashSet, io::Cursor}; + + let mut config = ResolvConf::new(); + let mut name_servers = String::new(); + + let mut dns_servers = HashSet::new(); + for adapter in ipconfig::get_adapters()? { + for dns in adapter.dns_servers() { + dns_servers.insert(dns.to_owned()); + } + } + + for dns in &dns_servers { + name_servers.push_str(&format!("nameserver {}\n", dns)); + } + + let mut name_servers = Cursor::new(name_servers.into_bytes()); + config.parse(&mut name_servers)?; + config.finalize(); + + Ok(StubResolver::from_conf(config)) +} + pub async fn resolve(domain: &str) -> Result { let mut dnslink = "_dnslink.".to_string(); dnslink.push_str(domain); + let resolver = create_resolver()?; + let qname = Dname::::from_str(&domain)?; let question = Question::new_in(qname, Rtype::Txt); - let resolver = StubResolver::new(); - let query1 = Box::pin(async move { resolver.query(question).await }); + let resolver1 = resolver.clone(); + let query1 = Box::pin(async move { resolver1.query(question).await }); let qname = Dname::::from_str(&dnslink)?; let question = Question::new_in(qname, Rtype::Txt); - let resolver = StubResolver::new(); - let query2 = Box::pin(async move { resolver.query(question).await }); + let resolver2 = resolver; + let query2 = Box::pin(async move { resolver2.query(question).await }); Ok(DnsLinkFuture { query: select_ok(vec![query1 as FutureAnswer, query2]),