From 2693e0eac2d4e6b363f2ede762fe7d0b18eba4e5 Mon Sep 17 00:00:00 2001
From: Brian Picciano
Date: Mon, 10 Jul 2023 18:22:37 +0200
Subject: [PATCH] Implement support for AAAA records
---
.dev-config.yml | 2 +
README.md | 7 ++-
src/domain/checker.rs | 30 +++++++++++--
src/main.rs | 4 ++
src/service.rs | 6 ++-
src/service/http.rs | 17 ++-----
src/service/http/tpl/domain_init.html | 64 +++++++++++++++++++--------
7 files changed, 91 insertions(+), 39 deletions(-)
diff --git a/.dev-config.yml b/.dev-config.yml
index 407a94a..bc9fa6d 100644
--- a/.dev-config.yml
+++ b/.dev-config.yml
@@ -7,3 +7,5 @@ service:
dns_records:
- type: A
addr: 127.0.0.1
+ - type: AAAA
+ addr: ::1
diff --git a/README.md b/README.md
index ed5698d..19079a9 100644
--- a/README.md
+++ b/README.md
@@ -66,10 +66,13 @@ service:
#- type: A
# addr: 127.0.0.1
+ #- type: AAAA
+ # addr: ::1
+
# The domain name which will be used to serve the web interface of Domani. If
# service.http.https_addr is enabled then an HTTPS certificate for this domain
# will be retrieved automatically.
- # primary_domain: "localhost"
+ #primary_domain: "localhost"
#http:
@@ -113,7 +116,7 @@ Within the shell which opens you can do `cargo run` to start a local instance.
## Roadmap
-* Support for AAAA and CNAME records
+* Support for CNAME records
* Support for more backends than just git repositories, including:
* IPFS/IPNS
* Alternative URLs (reverse proxy)
diff --git a/src/domain/checker.rs b/src/domain/checker.rs
index a2ab465..702957f 100644
--- a/src/domain/checker.rs
+++ b/src/domain/checker.rs
@@ -23,9 +23,33 @@ pub enum CheckDomainError {
pub enum DNSRecord {
A(net::Ipv4Addr),
+ AAAA(net::Ipv6Addr),
}
impl DNSRecord {
+ async fn check_aaaa(
+ client: &mut AsyncClient,
+ domain: &trust_dns_client::rr::Name,
+ addr: &net::Ipv6Addr,
+ ) -> Result {
+ let response = client
+ .query(domain.clone(), DNSClass::IN, RecordType::AAAA)
+ .await
+ .or_unexpected_while("querying A record")?;
+
+ let records = response.answers();
+
+ if records.len() != 1 {
+ return Ok(false);
+ }
+
+ // if the single record isn't a AAAA, or it's not the target AAAA, then return false
+ match records[0].data() {
+ Some(RData::AAAA(remote_addr)) if remote_addr == addr => Ok(true),
+ _ => return Ok(false),
+ }
+ }
+
async fn check_a(
client: &mut AsyncClient,
domain: &trust_dns_client::rr::Name,
@@ -42,10 +66,9 @@ impl DNSRecord {
return Ok(false);
}
- // if the single record isn't a A, or it's not the target A, then return
- // TargetANAMENotSet
+ // if the single record isn't a A, or it's not the target A, then return false
match records[0].data() {
- Some(RData::A(remote_a)) if remote_a == addr => Ok(true),
+ Some(RData::A(remote_addr)) if remote_addr == addr => Ok(true),
_ => return Ok(false),
}
}
@@ -57,6 +80,7 @@ impl DNSRecord {
) -> Result {
match self {
Self::A(addr) => Self::check_a(client, domain, &addr).await,
+ Self::AAAA(addr) => Self::check_aaaa(client, domain, &addr).await,
}
}
}
diff --git a/src/main.rs b/src/main.rs
index a056441..1935039 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -55,6 +55,10 @@ async fn main() {
let domain_checker = {
let dns_records = config.service.dns_records.clone();
+ if dns_records.len() == 0 {
+ panic!("service.dns_records must have at least one record defined")
+ }
+
domani::domain::checker::DNSChecker::new(
&config.domain.dns,
dns_records.into_iter().map(|r| r.into()).collect(),
diff --git a/src/service.rs b/src/service.rs
index 6b96d0b..bb3c5dd 100644
--- a/src/service.rs
+++ b/src/service.rs
@@ -2,23 +2,25 @@ pub mod http;
mod util;
use crate::domain;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::{net, str::FromStr};
fn default_primary_domain() -> domain::Name {
domain::Name::from_str("localhost").unwrap()
}
-#[derive(Deserialize, Clone)]
+#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "type")]
pub enum ConfigDNSRecord {
A { addr: net::Ipv4Addr },
+ AAAA { addr: net::Ipv6Addr },
}
impl From for domain::checker::DNSRecord {
fn from(r: ConfigDNSRecord) -> Self {
match r {
ConfigDNSRecord::A { addr } => Self::A(addr),
+ ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr),
}
}
}
diff --git a/src/service/http.rs b/src/service/http.rs
index 26607b7..ada0aed 100644
--- a/src/service/http.rs
+++ b/src/service/http.rs
@@ -8,7 +8,7 @@ use hyper::{Body, Method, Request, Response};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
-use std::{future, net, sync};
+use std::{future, sync};
use crate::error::unexpected;
use crate::{domain, service, util};
@@ -225,10 +225,10 @@ impl<'svc> Service {
domain_config: service::util::FlatConfig,
) -> Response {
#[derive(Serialize)]
- struct Response {
+ struct Response<'a> {
domain: domain::Name,
flat_config: service::util::FlatConfig,
- target_a: net::Ipv4Addr,
+ dns_records: &'a [service::ConfigDNSRecord],
challenge_token: String,
}
@@ -249,21 +249,12 @@ impl<'svc> Service {
}
};
- let target_a = match self
- .config
- .dns_records
- .get(0)
- .expect("at least one target record expected")
- {
- service::ConfigDNSRecord::A { addr } => addr.clone(),
- };
-
self.render_page(
"/domain_init.html",
Response {
domain: args.domain,
flat_config: config.into(),
- target_a: target_a,
+ dns_records: &self.config.dns_records,
challenge_token: config_hash,
},
)
diff --git a/src/service/http/tpl/domain_init.html b/src/service/http/tpl/domain_init.html
index 7b99ff6..897d497 100644
--- a/src/service/http/tpl/domain_init.html
+++ b/src/service/http/tpl/domain_init.html
@@ -1,23 +1,9 @@
Configure DNS
-
Next you will need to configure your DNS server to point to Domani. There
-are two entries you will need to add:
+
This step requires a passphrase that has been given to you by the
+administrator of the Domani server:
-
-
- A A {{ data.domain }} entry with the value
- {{ data.target_a }}
-
-
- A TXT _domani_challenge.{{ data.domain }} entry with the value
- {{ data.challenge_token }}
-
-
-
-
Once complete, you can hit the following button to check your configuration
-and set up your domain.
-
-
-
+
+
Next you will need to configure your DNS server to point to Domani. There are
+two entries you will need to add. The first entry tells the Domani server that
+it is allowed to serve this domain with your given configuration:
+
+
+
+
Type
+
Domain
+
Value
+
+
+
TXT
+
_domani_challenge.{{ data.domain }}
+
{{ data.challenge_token }}
+
+
+
+
The second entry ensures that other users find the Domani server when they
+query for your domain name. It can be one or more of:
+
+
+
+
Type
+
Domain
+
Value
+
+
+ {{ #each data.dns_records }}
+
+
{{ this.type }}
+
{{ lookup ../data "domain" }}
+
{{ this.addr }}
+
+ {{ /each }}
+
+
+
Once both entries are installed, you can hit the following button to check
+your configuration and set up your domain.