ipns: Implement dnslink.

This commit is contained in:
David Craven
2019-03-08 15:48:12 +01:00
parent 20b1b6c78c
commit 78e8a8fbce
6 changed files with 431 additions and 278 deletions

552
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,12 +6,13 @@ edition = "2018"
[dependencies]
byteorder = "*"
cbor = "*"
cid = "*"
cbor = { git = "https://github.com/dvc94ch/rust-cbor", branch = "read-data-item" }
cid = { git = "https://github.com/multiformats/rust-cid", branch = "master" }
domain = { path = "../domain/domain" }
env_logger = "*"
failure = "*"
fnv = "*"
futures-preview = { version = "0.3.0-alpha.13", features = ["compat"] }
futures-preview = { git = "https://github.com/rust-lang-nursery/futures-rs", branch = "master", features = ["compat"] }
libp2p = { version = "*", git = "https://github.com/libp2p/rust-libp2p", rev = "5655624" }
log = "*"
multibase = "*"
@ -25,24 +26,5 @@ rustc-serialize = "0.3"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
tokio = { version = "0.1", features = ["async-await-preview"] }
xdg = "*"
[patch.crates-io]
cbor = { git = "https://github.com/dvc94ch/rust-cbor", branch = "read-data-item" }
cid = { git = "https://github.com/dvc94ch/rust-cid", branch = "implement-hash" }
tokio = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-async-await = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-codec = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-current-thread = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-executor = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-fs = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-io = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-reactor = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-sync = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-threadpool = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-timer = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-tcp = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-tls = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-udp = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio-uds = { git = "https://github.com/dvc94ch/tokio", branch = "2018-edition" }
tokio = { version = "0.1", features = ["async-await-preview"] }
xdg = "*"

View File

@ -1,5 +1,5 @@
#![feature(async_await, await_macro, futures_api)]
use ipfs::{Ipfs, IpfsOptions, PeerId, TestTypes};
use ipfs::{Ipfs, IpfsOptions, IpfsPath, PeerId, TestTypes};
fn main() {
let options = IpfsOptions::<TestTypes>::default();
@ -22,6 +22,14 @@ fn main() {
let new_ipfs_path = await!(ipfs.resolve_ipns(&ipns_path)).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 = await!(ipfs.resolve_ipns(&ipfs_path)).unwrap();
println!("Resolved stage 1: {:?}", ipfs_path.to_string());
let ipfs_path = await!(ipfs.resolve_ipns(&ipfs_path)).unwrap();
println!("Resolved stage 2: {:?}", ipfs_path.to_string());
ipfs.exit_daemon();
});
}

89
src/ipns/dns.rs Normal file
View File

@ -0,0 +1,89 @@
use crate::error::Error;
use crate::path::IpfsPath;
use domain::core::bits::{Dname, Question};
use domain::core::iana::Rtype;
use domain::core::rdata::Txt;
use domain::resolv::{Resolver, StubResolver};
use domain::resolv::stub::resolver::Query;
use std::future::Future;
use std::pin::Pin;
use std::task::{Poll, Waker};
use std::str::FromStr;
use tokio::prelude::{Async, Future as FutureOld, future::SelectOk, future::select_ok};
#[derive(Debug, Fail)]
#[fail(display = "no dnslink entry")]
pub struct DnsLinkError;
pub struct DnsLinkFuture {
query: SelectOk<Query>,
}
impl Future for DnsLinkFuture {
type Output = Result<IpfsPath, Error>;
fn poll(self: Pin<&mut Self>, _waker: &Waker) -> Poll<Self::Output> {
let _self = self.get_mut();
loop {
let poll = _self.query.poll();
if poll.is_err() {
return Poll::Ready(Err(DnsLinkError.into()));
}
match poll.unwrap() {
Async::Ready((answer, rest)) => {
for record in answer.answer()?.limit_to::<Txt>() {
let txt = record?;
let bytes = txt.data().text();
let string = String::from_utf8_lossy(&bytes).to_string();
if string.starts_with("dnslink=") {
let path = IpfsPath::from_str(&string[8..])?;
return Poll::Ready(Ok(path));
}
}
if rest.len() > 0 {
_self.query = select_ok(rest);
} else {
return Poll::Ready(Err(DnsLinkError.into()))
}
}
Async::NotReady => return Poll::Pending,
}
}
}
}
pub fn resolve(domain: &str) -> Result<DnsLinkFuture, Error> {
let mut dnslink = "_dnslink.".to_string();
dnslink.push_str(domain);
let qname = Dname::from_str(&dnslink[9..])?;
let question = Question::new_in(qname, Rtype::Txt);
let query1 = StubResolver::new().query(question);
let qname = Dname::from_str(&dnslink)?;
let question = Question::new_in(qname, Rtype::Txt);
let query2 = StubResolver::new().query(question);
Ok(DnsLinkFuture {
query: select_ok(vec![query1, query2]),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve1() {
tokio::run_async(async {
let res = await!(resolve("ipfs.io").unwrap()).unwrap().to_string();
assert_eq!(res, "/ipns/website.ipfs.io");
})
}
fn test_resolve2() {
tokio::run_async(async {
let res = await!(resolve("website.ipfs.io").unwrap()).unwrap().to_string();
assert_eq!(res, "/ipfs/QmYfHCcUQBjyvrLfQ8Cnt2YAEiLDNRqMXAeHndM6fDW8yB");
})
}
}

View File

@ -5,8 +5,9 @@ use crate::repo::{Repo, RepoTypes};
use libp2p::PeerId;
use std::future::Future;
mod ipns_pb;
mod dns;
mod entry;
mod ipns_pb;
pub struct Ipns<Types: RepoTypes> {
repo: Repo<Types>,
@ -26,10 +27,17 @@ impl<Types: RepoTypes> Ipns<Types> {
let repo = self.repo.clone();
let path = path.to_owned();
async move {
if path.root().is_ipns() {
Ok(await!(repo.get_ipns(path.root().peer_id()?))??)
} else {
Ok(path)
match path.root() {
PathRoot::Ipld(_) => Ok(path),
PathRoot::Ipns(peer_id) => {
match await!(repo.get_ipns(peer_id))? {
Some(path) => Ok(path),
None => bail!("unimplemented"),
}
},
PathRoot::Dns(domain) => {
Ok(await!(dns::resolve(domain)?)?)
},
}
}
}

View File

@ -30,7 +30,12 @@ impl IpfsPath {
let root = match (empty, root_type, key) {
(Some(""), Some("ipfs"), Some(key)) => PathRoot::Ipld(Cid::from(key)?),
(Some(""), Some("ipld"), Some(key)) => PathRoot::Ipld(Cid::from(key)?),
(Some(""), Some("ipns"), Some(key)) => PathRoot::Ipns(PeerId::from_str(key).ok()?),
(Some(""), Some("ipns"), Some(key)) => {
match PeerId::from_str(key).ok() {
Some(peer_id) => PathRoot::Ipns(peer_id),
None => PathRoot::Dns(key.to_string())
}
},
_ => return Err(IpfsPathError::InvalidPath(string.to_owned()).into()),
};
let mut path = IpfsPath::new(root);
@ -133,6 +138,7 @@ impl TryInto<PeerId> for IpfsPath {
pub enum PathRoot {
Ipld(Cid),
Ipns(PeerId),
Dns(String),
}
impl PathRoot {
@ -168,6 +174,7 @@ impl PathRoot {
let (prefix, key) = match self {
PathRoot::Ipld(cid) => ("/ipfs/", cid.to_string()),
PathRoot::Ipns(peer_id) => ("/ipns/", peer_id.to_base58()),
PathRoot::Dns(domain) => ("/ipns/", domain.to_owned()),
};
let mut string = prefix.to_string();
string.push_str(&key);
@ -178,6 +185,7 @@ impl PathRoot {
match self {
PathRoot::Ipld(cid) => cid.to_bytes(),
PathRoot::Ipns(peer_id) => peer_id.as_bytes().to_vec(),
PathRoot::Dns(domain) => domain.as_bytes().to_vec(),
}
}
}