Skip to content
This repository was archived by the owner on Oct 23, 2022. It is now read-only.

Commit 0019ece

Browse files
bors[bot]ljedrz
andauthored
Merge #353
353: Implement /dns and /resolve r=ljedrz a=ljedrz Add a rudimentary implementation of the `/dns` and `/resolve` endpoints; putting it out there already, as due to the similarity of these two endpoints I'm not 100% sure how much we want to "condense" their inner workings. This upgrades or conformance suite stats from ``` 170 passing 50 pending ``` to ``` 178 passing 42 pending ``` Co-authored-by: ljedrz <[email protected]>
2 parents 90fa9a3 + 3174225 commit 0019ece

File tree

10 files changed

+245
-37
lines changed

10 files changed

+245
-37
lines changed

Cargo.lock

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ tracing = { default-features = false, features = ["log"], version = "0.1" }
3939
tracing-futures = { default-features = false, features = ["std", "futures-03"], version = "0.2" }
4040
void = { default-features = false, version = "1.0" }
4141

42+
[target.'cfg(windows)'.dependencies]
43+
# required for DNS resolution
44+
ipconfig = { default-features = false, version = "0.2" }
45+
4246
[build-dependencies]
4347
prost-build = { default-features = false, version = "0.6" }
4448

conformance/test/index.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,16 @@ const factory = createFactory(options)
3333
// Phase 1.0-ish
3434
//
3535
tests.miscellaneous(factory, { skip: [
36-
'dns',
37-
'resolve',
36+
// the cidBase param is not implemented yet
37+
'should resolve an IPFS hash and return a base64url encoded CID in path',
38+
// different Cid, the /path/to/testfile.txt suffix shouldn't be there
39+
'should resolve an IPFS path link',
40+
// different Cid, missing "/path/to" in the middle
41+
'should resolve up to the last node across multiple nodes',
42+
// expected "true", got "false"
43+
'should resolve an IPNS DNS link',
44+
// HTTP: not implemented
45+
'should resolve IPNS link recursively',
3846
// these cause a hang 20% of time:
3947
'should respect timeout option when getting the node id',
4048
'should respect timeout option when getting the node version',

examples/ipfs_ipns_test.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ async fn main() {
2121
.unwrap();
2222

2323
// Resolve a Block
24-
let new_ipfs_path = ipfs.resolve_ipns(&ipns_path).await.unwrap();
24+
let new_ipfs_path = ipfs.resolve_ipns(&ipns_path, false).await.unwrap();
2525
assert_eq!(ipfs_path, new_ipfs_path);
2626

2727
// Resolve dnslink
2828
let ipfs_path = IpfsPath::from_str("/ipns/ipfs.io").unwrap();
2929
println!("Resolving {:?}", ipfs_path.to_string());
30-
let ipfs_path = ipfs.resolve_ipns(&ipfs_path).await.unwrap();
30+
let ipfs_path = ipfs.resolve_ipns(&ipfs_path, false).await.unwrap();
3131
println!("Resolved stage 1: {:?}", ipfs_path.to_string());
32-
let ipfs_path = ipfs.resolve_ipns(&ipfs_path).await.unwrap();
32+
let ipfs_path = ipfs.resolve_ipns(&ipfs_path, false).await.unwrap();
3333
println!("Resolved stage 2: {:?}", ipfs_path.to_string());
3434

3535
ipfs.exit_daemon().await;

http/src/v0.rs

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod block;
66
pub mod dag;
77
pub mod dht;
88
pub mod id;
9+
pub mod ipns;
910
pub mod pin;
1011
pub mod pubsub;
1112
pub mod refs;
@@ -87,9 +88,11 @@ pub fn routes<T: IpfsTypes>(
8788
and_boxed!(warp::path!("id"), id::identity(ipfs)),
8889
and_boxed!(warp::path!("add"), root_files::add(ipfs)),
8990
and_boxed!(warp::path!("cat"), root_files::cat(ipfs)),
91+
and_boxed!(warp::path!("dns"), ipns::dns(ipfs)),
9092
and_boxed!(warp::path!("get"), root_files::get(ipfs)),
9193
and_boxed!(warp::path!("refs" / "local"), refs::local(ipfs)),
9294
and_boxed!(warp::path!("refs"), refs::refs(ipfs)),
95+
and_boxed!(warp::path!("resolve"), ipns::resolve(ipfs)),
9396
warp::path!("version")
9497
.and(query::<version::Query>())
9598
.and_then(version::version),

http/src/v0/ipns.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use crate::v0::support::{with_ipfs, StringError, StringSerialized};
2+
use ipfs::{Ipfs, IpfsPath, IpfsTypes};
3+
use serde::{Deserialize, Serialize};
4+
use warp::{query, Filter, Rejection, Reply};
5+
6+
#[derive(Debug, Deserialize)]
7+
pub struct ResolveQuery {
8+
// the name to resolve
9+
arg: StringSerialized<IpfsPath>,
10+
#[serde(rename = "dht-record-count")]
11+
dht_record_count: Option<usize>,
12+
#[serde(rename = "dht-timeout")]
13+
dht_timeout: Option<String>,
14+
}
15+
16+
pub fn resolve<T: IpfsTypes>(
17+
ipfs: &Ipfs<T>,
18+
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
19+
with_ipfs(ipfs)
20+
.and(query::<ResolveQuery>())
21+
.and_then(resolve_query)
22+
}
23+
24+
async fn resolve_query<T: IpfsTypes>(
25+
ipfs: Ipfs<T>,
26+
query: ResolveQuery,
27+
) -> Result<impl Reply, Rejection> {
28+
let ResolveQuery { arg, .. } = query;
29+
let name = arg.into_inner();
30+
let path = ipfs
31+
.resolve_ipns(&name, false)
32+
.await
33+
.map_err(StringError::from)?
34+
.to_string();
35+
36+
let response = ResolveResponse { path };
37+
38+
Ok(warp::reply::json(&response))
39+
}
40+
41+
#[derive(Debug, Serialize)]
42+
#[serde(rename_all = "PascalCase")]
43+
struct ResolveResponse {
44+
path: String,
45+
}
46+
47+
#[derive(Debug, Deserialize)]
48+
pub struct DnsQuery {
49+
// the name to resolve
50+
arg: String,
51+
recursive: Option<bool>,
52+
}
53+
54+
pub fn dns<T: IpfsTypes>(
55+
ipfs: &Ipfs<T>,
56+
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
57+
with_ipfs(ipfs).and(query::<DnsQuery>()).and_then(dns_query)
58+
}
59+
60+
async fn dns_query<T: IpfsTypes>(ipfs: Ipfs<T>, query: DnsQuery) -> Result<impl Reply, Rejection> {
61+
let DnsQuery { arg, recursive } = query;
62+
// attempt to parse the argument prepended with "/ipns/" if it fails to parse like a compliant
63+
// IpfsPath and there is no leading slash
64+
let path = if !arg.starts_with('/') {
65+
if let Ok(parsed) = arg.parse() {
66+
Ok(parsed)
67+
} else {
68+
format!("/ipns/{}", arg).parse()
69+
}
70+
} else {
71+
arg.parse()
72+
}
73+
.map_err(StringError::from)?;
74+
75+
let path = ipfs
76+
.resolve_ipns(&path, recursive.unwrap_or(false))
77+
.await
78+
.map_err(StringError::from)?
79+
.to_string();
80+
81+
let response = DnsResponse { path };
82+
83+
Ok(warp::reply::json(&response))
84+
}
85+
86+
#[derive(Debug, Serialize)]
87+
#[serde(rename_all = "PascalCase")]
88+
struct DnsResponse {
89+
path: String,
90+
}

src/dag.rs

+29-11
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ pub enum ResolveError {
4646
/// Path attempted to resolve through a property, index or link which did not exist.
4747
#[error("no link named {:?} under {0}", .1.iter().last().unwrap())]
4848
NotFound(Cid, SlashedPath),
49+
50+
/// Tried to use a path neiter containing nor resolving to a Cid.
51+
#[error("the path neiter contains nor resolves to a Cid")]
52+
NoCid(IpfsPath),
53+
54+
/// Couldn't resolve a path via IPNS.
55+
#[error("can't resolve an IPNS path")]
56+
IpnsResolutionFailed(IpfsPath),
4957
}
5058

5159
#[derive(Debug, Error)]
@@ -178,19 +186,24 @@ impl<Types: RepoTypes> IpldDag<Types> {
178186
///
179187
/// Returns the resolved node as `Ipld`.
180188
pub async fn get(&self, path: IpfsPath) -> Result<Ipld, ResolveError> {
181-
// FIXME: do ipns resolve first
182-
let cid = match path.root().cid() {
189+
let resolved_path = self
190+
.ipfs
191+
.resolve_ipns(&path, true)
192+
.await
193+
.map_err(|_| ResolveError::IpnsResolutionFailed(path))?;
194+
195+
let cid = match resolved_path.root().cid() {
183196
Some(cid) => cid,
184-
None => panic!("Ipns resolution not implemented; expected a Cid-based path"),
197+
None => return Err(ResolveError::NoCid(resolved_path)),
185198
};
186199

187-
let mut iter = path.iter().peekable();
200+
let mut iter = resolved_path.iter().peekable();
188201

189202
let (node, _) = match self.resolve0(cid, &mut iter, true).await {
190203
Ok(t) => t,
191204
Err(e) => {
192205
drop(iter);
193-
return Err(e.with_path(path));
206+
return Err(e.with_path(resolved_path));
194207
}
195208
};
196209

@@ -213,27 +226,32 @@ impl<Types: RepoTypes> IpldDag<Types> {
213226
path: IpfsPath,
214227
follow_links: bool,
215228
) -> Result<(ResolvedNode, SlashedPath), ResolveError> {
216-
// FIXME: do ipns resolve first
217-
let cid = match path.root().cid() {
229+
let resolved_path = self
230+
.ipfs
231+
.resolve_ipns(&path, true)
232+
.await
233+
.map_err(|_| ResolveError::IpnsResolutionFailed(path))?;
234+
235+
let cid = match resolved_path.root().cid() {
218236
Some(cid) => cid,
219-
None => panic!("Ipns resolution not implemented; expected a Cid-based path"),
237+
None => return Err(ResolveError::NoCid(resolved_path)),
220238
};
221239

222240
let (node, matched_segments) = {
223-
let mut iter = path.iter().peekable();
241+
let mut iter = resolved_path.iter().peekable();
224242
match self.resolve0(cid, &mut iter, follow_links).await {
225243
Ok(t) => t,
226244
Err(e) => {
227245
drop(iter);
228-
return Err(e.with_path(path));
246+
return Err(e.with_path(resolved_path));
229247
}
230248
}
231249
};
232250

233251
// we only care about returning this remaining_path with segments up until the last
234252
// document but it can and should contain all of the following segments (if any). there
235253
// could be more segments when `!follow_links`.
236-
let remaining_path = path.into_shifted(matched_segments);
254+
let remaining_path = resolved_path.into_shifted(matched_segments);
237255

238256
Ok((node, remaining_path))
239257
}

src/ipns/dns.rs

+53-12
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@ use domain_resolv::{stub::Answer, StubResolver};
88
use futures::future::{select_ok, SelectOk};
99
use futures::pin_mut;
1010
use std::future::Future;
11-
use std::io;
1211
use std::pin::Pin;
1312
use std::str::FromStr;
1413
use std::task::{Context, Poll};
15-
use thiserror::Error;
14+
use std::{fmt, io};
1615

17-
#[derive(Debug, Error)]
18-
#[error("no dnslink entry")]
19-
pub struct DnsLinkError;
16+
#[derive(Debug)]
17+
pub struct DnsLinkError(String);
2018

21-
type FutureAnswer = Pin<Box<dyn Future<Output = Result<Answer, io::Error>>>>;
19+
impl fmt::Display for DnsLinkError {
20+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
21+
write!(fmt, "DNS error: {}", self.0)
22+
}
23+
}
24+
25+
impl std::error::Error for DnsLinkError {}
26+
27+
type FutureAnswer = Pin<Box<dyn Future<Output = Result<Answer, io::Error>> + Send>>;
2228

2329
pub struct DnsLinkFuture {
2430
query: SelectOk<FutureAnswer>,
@@ -46,28 +52,63 @@ impl Future for DnsLinkFuture {
4652
if !rest.is_empty() {
4753
_self.query = select_ok(rest);
4854
} else {
49-
return Poll::Ready(Err(DnsLinkError.into()));
55+
return Poll::Ready(Err(
56+
DnsLinkError("no DNS records found".to_owned()).into()
57+
));
5058
}
5159
}
5260
Poll::Pending => return Poll::Pending,
53-
Poll::Ready(Err(_)) => return Poll::Ready(Err(DnsLinkError.into())),
61+
Poll::Ready(Err(e)) => return Poll::Ready(Err(DnsLinkError(e.to_string()).into())),
5462
}
5563
}
5664
}
5765
}
5866

67+
#[cfg(not(target_os = "windows"))]
68+
fn create_resolver() -> Result<StubResolver, Error> {
69+
Ok(StubResolver::default())
70+
}
71+
72+
#[cfg(target_os = "windows")]
73+
fn create_resolver() -> Result<StubResolver, Error> {
74+
use domain_resolv::stub::conf::ResolvConf;
75+
use std::{collections::HashSet, io::Cursor};
76+
77+
let mut config = ResolvConf::new();
78+
let mut name_servers = String::new();
79+
80+
let mut dns_servers = HashSet::new();
81+
for adapter in ipconfig::get_adapters()? {
82+
for dns in adapter.dns_servers() {
83+
dns_servers.insert(dns.to_owned());
84+
}
85+
}
86+
87+
for dns in &dns_servers {
88+
name_servers.push_str(&format!("nameserver {}\n", dns));
89+
}
90+
91+
let mut name_servers = Cursor::new(name_servers.into_bytes());
92+
config.parse(&mut name_servers)?;
93+
config.finalize();
94+
95+
Ok(StubResolver::from_conf(config))
96+
}
97+
5998
pub async fn resolve(domain: &str) -> Result<IpfsPath, Error> {
6099
let mut dnslink = "_dnslink.".to_string();
61100
dnslink.push_str(domain);
101+
let resolver = create_resolver()?;
102+
62103
let qname = Dname::<Bytes>::from_str(&domain)?;
63104
let question = Question::new_in(qname, Rtype::Txt);
64-
let resolver = StubResolver::new();
65-
let query1 = Box::pin(async move { resolver.query(question).await });
105+
let resolver1 = resolver.clone();
106+
let query1 = Box::pin(async move { resolver1.query(question).await });
66107

67108
let qname = Dname::<Bytes>::from_str(&dnslink)?;
68109
let question = Question::new_in(qname, Rtype::Txt);
69-
let resolver = StubResolver::new();
70-
let query2 = Box::pin(async move { resolver.query(question).await });
110+
let resolver2 = resolver;
111+
let query2 = Box::pin(async move { resolver2.query(question).await });
71112

72113
Ok(DnsLinkFuture {
73114
query: select_ok(vec![query1 as FutureAnswer, query2]),

0 commit comments

Comments
 (0)