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: Support eth domains #543

Merged
merged 7 commits into from
Nov 26, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions iroh-gateway/src/bad_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ mod tests {
use hex_literal::hex;
use http::StatusCode;
use iroh_resolver::content_loader::{FullLoader, FullLoaderConfig, GatewayUrl};
use iroh_resolver::dns_resolver::Config as DnsResolverConfig;
use iroh_rpc_client::{Client as RpcClient, Config as RpcClientConfig};

#[tokio::test]
Expand Down Expand Up @@ -205,6 +206,7 @@ mod tests {
rpc_addr,
Arc::new(Some(RwLock::new(bbits))),
content_loader,
DnsResolverConfig::default(),
)
.await
.unwrap();
Expand Down
5 changes: 3 additions & 2 deletions iroh-gateway/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use iroh_metrics::{
gateway::{GatewayHistograms, GatewayMetrics},
observe, record,
};
use iroh_resolver::dns_resolver::Config;
use iroh_resolver::{
content_loader::ContentLoader,
resolver::{
Expand Down Expand Up @@ -90,9 +91,9 @@ impl<T: ContentLoader + std::marker::Unpin> http_body::Body for PrettyStreamBody
}

impl<T: ContentLoader + std::marker::Unpin> Client<T> {
pub fn new(rpc_client: &T) -> Self {
pub fn new(rpc_client: &T, dns_resolver_config: Config) -> Self {
Self {
resolver: Resolver::new(rpc_client.clone()),
resolver: Resolver::with_dns_resolver(rpc_client.clone(), dns_resolver_config),
}
}

Expand Down
8 changes: 7 additions & 1 deletion iroh-gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use headers::{
AccessControlAllowHeaders, AccessControlAllowMethods, AccessControlAllowOrigin, HeaderMapExt,
};
use iroh_metrics::config::Config as MetricsConfig;
use iroh_resolver::dns_resolver::Config as DnsResolverConfig;
use iroh_rpc_client::Config as RpcClientConfig;
use iroh_rpc_types::{gateway::GatewayServerAddr, Addr};
use iroh_util::insert_into_config_map;
Expand All @@ -29,10 +30,13 @@ pub struct Config {
/// flag to toggle whether the gateway should use denylist on requests
pub use_denylist: bool,
/// URL of gateways to be used by the racing resolver.
/// strings can either be urls or subdomain gateway roots
/// Strings can either be urls or subdomain gateway roots
/// values without https:// prefix are treated as subdomain gateways (eg: dweb.link)
/// values with are treated as IPFS path gateways (eg: https://ipfs.io)
pub http_resolvers: Option<Vec<String>>,
/// Separate resolvers for particular TLDs
#[serde(default = "DnsResolverConfig::default")]
pub dns_resolver: DnsResolverConfig,
/// Indexer node to use.
pub indexer_endpoint: Option<String>,
/// rpc addresses for the gateway & addresses for the rpc client to dial
Expand All @@ -54,6 +58,7 @@ impl Config {
port,
rpc_client,
http_resolvers: None,
dns_resolver: DnsResolverConfig::default(),
indexer_endpoint: None,
metrics: MetricsConfig::default(),
use_denylist: false,
Expand Down Expand Up @@ -123,6 +128,7 @@ impl Default for Config {
port: DEFAULT_PORT,
rpc_client,
http_resolvers: None,
dns_resolver: DnsResolverConfig::default(),
indexer_endpoint: None,
metrics: MetricsConfig::default(),
use_denylist: false,
Expand Down
19 changes: 14 additions & 5 deletions iroh-gateway/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use axum::Router;
use iroh_resolver::content_loader::ContentLoader;
use iroh_rpc_types::gateway::GatewayServerAddr;

use iroh_resolver::dns_resolver::Config as DnsResolverConfig;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;

Expand Down Expand Up @@ -33,6 +34,7 @@ impl<T: ContentLoader + std::marker::Unpin> Core<T> {
rpc_addr: GatewayServerAddr,
bad_bits: Arc<Option<RwLock<BadBits>>>,
content_loader: T,
dns_resolver_config: DnsResolverConfig,
) -> anyhow::Result<Self> {
tokio::spawn(async move {
if let Err(err) = rpc::new(rpc_addr, Gateway::default()).await {
Expand All @@ -48,7 +50,7 @@ impl<T: ContentLoader + std::marker::Unpin> Core<T> {
"not_found".to_string(),
templates::NOT_FOUND_TEMPLATE.to_string(),
);
let client = Client::<T>::new(&content_loader);
let client = Client::<T>::new(&content_loader, dns_resolver_config);

Ok(Self {
state: Arc::new(State {
Expand Down Expand Up @@ -76,6 +78,7 @@ impl<T: ContentLoader + std::marker::Unpin> Core<T> {
config: Arc<dyn StateConfig>,
bad_bits: Arc<Option<RwLock<BadBits>>>,
content_loader: T,
dns_resolver_config: DnsResolverConfig,
) -> anyhow::Result<Arc<State<T>>> {
let mut templates = HashMap::new();
templates.insert(
Expand All @@ -86,7 +89,7 @@ impl<T: ContentLoader + std::marker::Unpin> Core<T> {
"not_found".to_string(),
templates::NOT_FOUND_TEMPLATE.to_string(),
);
let client = Client::new(&content_loader);
let client = Client::new(&content_loader, dns_resolver_config);
Ok(Arc::new(State {
config,
client,
Expand Down Expand Up @@ -147,9 +150,15 @@ mod tests {
};
let content_loader =
FullLoader::new(rpc_client.clone(), loader_config).expect("invalid config");
let core = Core::new(config, rpc_addr, Arc::new(None), content_loader)
.await
.unwrap();
let core = Core::new(
config,
rpc_addr,
Arc::new(None),
content_loader,
DnsResolverConfig::default(),
)
.await
.unwrap();
let server = core.server();
let addr = server.local_addr();
let core_task = tokio::spawn(async move {
Expand Down
2 changes: 2 additions & 0 deletions iroh-gateway/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async fn main() -> Result<()> {
println!("{:#?}", config);

let metrics_config = config.metrics.clone();
let dns_resolver_config = config.dns_resolver.clone();
let bad_bits = match config.use_denylist {
true => Arc::new(Some(RwLock::new(BadBits::new()))),
false => Arc::new(None),
Expand Down Expand Up @@ -70,6 +71,7 @@ async fn main() -> Result<()> {
rpc_addr,
Arc::clone(&bad_bits),
content_loader,
dns_resolver_config,
)
.await?;

Expand Down
1 change: 1 addition & 0 deletions iroh-one/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ async fn main() -> Result<()> {
Arc::new(config.clone()),
Arc::clone(&bad_bits),
content_loader,
config.gateway.dns_resolver,
)
.await?;

Expand Down
2 changes: 1 addition & 1 deletion iroh-resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ serde_json = "1.0.87"
tokio = { version = "1", features = ["fs"] }
tokio-util = { version = "0.7", features = ["io"] }
tracing = "0.1.34"
trust-dns-resolver = { version = "0.22.0", features = ["tokio-runtime"] }
trust-dns-resolver = { version = "0.22.0", features = ["dns-over-https-rustls", "serde-config", "tokio-runtime"] }
unsigned-varint = "0.7.1"

[dev-dependencies]
Expand Down
159 changes: 159 additions & 0 deletions iroh-resolver/src/dns_resolver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr};

use anyhow::Result;
use serde::{Deserialize, Serialize};
use trust_dns_resolver::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts};
use trust_dns_resolver::{AsyncResolver, TokioAsyncResolver};
dignifiedquire marked this conversation as resolved.
Show resolved Hide resolved

use crate::resolver::Path;

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Config {
/// Mapping from TLD to the specific instance of resolver
tld_resolvers: Option<HashMap<String, ResolverConfig>>,
}

impl Config {
pub fn empty() -> Self {
Config {
tld_resolvers: None,
}
}
}

impl Default for Config {
fn default() -> Self {
Config {
/// Documentation on .eth TLD lives on https://eth.link/
tld_resolvers: Some(HashMap::from_iter(vec![(
dignifiedquire marked this conversation as resolved.
Show resolved Hide resolved
"eth".to_string(),
ResolverConfig::from_parts(
None,
vec![],
NameServerConfigGroup::from_ips_https(
&[
IpAddr::V4(Ipv4Addr::new(104, 18, 165, 219)),
IpAddr::V4(Ipv4Addr::new(104, 18, 166, 219)),
],
443,
"resolver.cloudflare-eth.com".to_string(),
true,
),
),
)])),
}
}
}

#[derive(Debug)]
pub struct DnsResolver {
default_resolver: TokioAsyncResolver,
tld_resolvers: Option<HashMap<String, TokioAsyncResolver>>,
}

impl DnsResolver {
/// Creates resolver from its config
pub fn from_config(dns_resolver_config: Config) -> DnsResolver {
let tld_resolvers = dns_resolver_config
.tld_resolvers
.map(|dns_resolver_config| {
dns_resolver_config
.into_iter()
.map(|(tld, config)| {
(
tld,
AsyncResolver::tokio(config, ResolverOpts::default()).unwrap(),
)
})
.collect::<HashMap<_, _>>()
});
DnsResolver {
default_resolver: AsyncResolver::tokio(
ResolverConfig::default(),
ResolverOpts::default(),
)
.unwrap(),
tld_resolvers,
}
}

#[tracing::instrument]
pub async fn resolve_dnslink(&self, url: &str) -> Result<Vec<Path>> {
let url = format!("_dnslink.{}.", url);
let records = self.resolve_txt_record(&url).await?;
let records = records
.into_iter()
.filter(|r| r.starts_with("dnslink="))
.map(|r| {
let p = r.trim_start_matches("dnslink=").trim();
p.parse()
})
.collect::<Result<_>>()?;
Ok(records)
}

pub async fn resolve_txt_record(&self, url: &str) -> Result<Vec<String>> {
let tld = url.split('.').filter(|s| !s.is_empty()).last();
let resolver = tld
.and_then(|tld| {
self.tld_resolvers
.as_ref()
.and_then(|tld_resolvers| tld_resolvers.get(tld))
})
.unwrap_or(&self.default_resolver);
let txt_response = resolver.txt_lookup(url).await?;
dignifiedquire marked this conversation as resolved.
Show resolved Hide resolved
let out = txt_response.into_iter().map(|r| r.to_string()).collect();
Ok(out)
}
}

impl Default for DnsResolver {
fn default() -> Self {
DnsResolver::from_config(Config::default())
}
}

#[cfg(test)]
mod tests {
use super::DnsResolver;
use crate::resolver::PathType;

#[tokio::test]
async fn test_resolve_txt_record() {
let resolver = DnsResolver::default();
let result = resolver
.resolve_txt_record("_dnslink.ipfs.io.")
.await
.unwrap();
assert!(!result.is_empty());
assert_eq!(result[0], "dnslink=/ipns/website.ipfs.io");

let result = resolver
.resolve_txt_record("_dnslink.website.ipfs.io.")
.await
.unwrap();
assert!(!result.is_empty());
assert!(&result[0].starts_with("dnslink=/ipfs"));
}

#[tokio::test]
async fn test_resolve_dnslink() {
let resolver = DnsResolver::default();
let result = resolver.resolve_dnslink("ipfs.io").await.unwrap();
assert!(!result.is_empty());
assert_eq!(result[0], "/ipns/website.ipfs.io".parse().unwrap());

let result = resolver.resolve_dnslink("website.ipfs.io").await.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].typ(), PathType::Ipfs);
}

#[tokio::test]
async fn test_resolve_eth_domain() {
let resolver = DnsResolver::default();
let result = resolver.resolve_dnslink("ipfs.eth").await.unwrap();
assert!(!result.is_empty());
assert_eq!(result[0].typ(), PathType::Ipfs);
}
}
1 change: 1 addition & 0 deletions iroh-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod balanced_tree;
pub mod chunker;
pub mod codecs;
pub mod content_loader;
pub mod dns_resolver;
pub mod hamt;
pub mod indexer;
pub mod resolver;
Expand Down
Loading