Implement support for AAAA records

This commit is contained in:
Brian Picciano 2023-07-10 18:22:37 +02:00
parent 7d64f44dab
commit 8600c1050e
7 changed files with 90 additions and 39 deletions

View File

@ -7,3 +7,5 @@ service:
dns_records: dns_records:
- type: A - type: A
addr: 127.0.0.1 addr: 127.0.0.1
- type: AAAA
addr: ::1

View File

@ -66,10 +66,13 @@ service:
#- type: A #- type: A
# addr: 127.0.0.1 # addr: 127.0.0.1
#- type: AAAA
# addr: ::1
# The domain name which will be used to serve the web interface of Domani. If # 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 # service.http.https_addr is enabled then an HTTPS certificate for this domain
# will be retrieved automatically. # will be retrieved automatically.
# primary_domain: "localhost" #primary_domain: "localhost"
#http: #http:
@ -113,7 +116,6 @@ Within the shell which opens you can do `cargo run` to start a local instance.
## Roadmap ## Roadmap
* Support for AAAA and CNAME records
* Support for more backends than just git repositories, including: * Support for more backends than just git repositories, including:
* IPFS/IPNS * IPFS/IPNS
* Alternative URLs (reverse proxy) * Alternative URLs (reverse proxy)

View File

@ -23,9 +23,33 @@ pub enum CheckDomainError {
pub enum DNSRecord { pub enum DNSRecord {
A(net::Ipv4Addr), A(net::Ipv4Addr),
AAAA(net::Ipv6Addr),
} }
impl DNSRecord { impl DNSRecord {
async fn check_aaaa(
client: &mut AsyncClient,
domain: &trust_dns_client::rr::Name,
addr: &net::Ipv6Addr,
) -> Result<bool, unexpected::Error> {
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( async fn check_a(
client: &mut AsyncClient, client: &mut AsyncClient,
domain: &trust_dns_client::rr::Name, domain: &trust_dns_client::rr::Name,
@ -42,10 +66,9 @@ impl DNSRecord {
return Ok(false); return Ok(false);
} }
// if the single record isn't a A, or it's not the target A, then return // if the single record isn't a A, or it's not the target A, then return false
// TargetANAMENotSet
match records[0].data() { 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), _ => return Ok(false),
} }
} }
@ -57,6 +80,7 @@ impl DNSRecord {
) -> Result<bool, unexpected::Error> { ) -> Result<bool, unexpected::Error> {
match self { match self {
Self::A(addr) => Self::check_a(client, domain, &addr).await, Self::A(addr) => Self::check_a(client, domain, &addr).await,
Self::AAAA(addr) => Self::check_aaaa(client, domain, &addr).await,
} }
} }
} }

View File

@ -55,6 +55,10 @@ async fn main() {
let domain_checker = { let domain_checker = {
let dns_records = config.service.dns_records.clone(); 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( domani::domain::checker::DNSChecker::new(
&config.domain.dns, &config.domain.dns,
dns_records.into_iter().map(|r| r.into()).collect(), dns_records.into_iter().map(|r| r.into()).collect(),

View File

@ -2,23 +2,25 @@ pub mod http;
mod util; mod util;
use crate::domain; use crate::domain;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use std::{net, str::FromStr}; use std::{net, str::FromStr};
fn default_primary_domain() -> domain::Name { fn default_primary_domain() -> domain::Name {
domain::Name::from_str("localhost").unwrap() domain::Name::from_str("localhost").unwrap()
} }
#[derive(Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum ConfigDNSRecord { pub enum ConfigDNSRecord {
A { addr: net::Ipv4Addr }, A { addr: net::Ipv4Addr },
AAAA { addr: net::Ipv6Addr },
} }
impl From<ConfigDNSRecord> for domain::checker::DNSRecord { impl From<ConfigDNSRecord> for domain::checker::DNSRecord {
fn from(r: ConfigDNSRecord) -> Self { fn from(r: ConfigDNSRecord) -> Self {
match r { match r {
ConfigDNSRecord::A { addr } => Self::A(addr), ConfigDNSRecord::A { addr } => Self::A(addr),
ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr),
} }
} }
} }

View File

@ -8,7 +8,7 @@ use hyper::{Body, Method, Request, Response};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr; use std::str::FromStr;
use std::{future, net, sync}; use std::{future, sync};
use crate::error::unexpected; use crate::error::unexpected;
use crate::{domain, service, util}; use crate::{domain, service, util};
@ -225,10 +225,10 @@ impl<'svc> Service {
domain_config: service::util::FlatConfig, domain_config: service::util::FlatConfig,
) -> Response<Body> { ) -> Response<Body> {
#[derive(Serialize)] #[derive(Serialize)]
struct Response { struct Response<'a> {
domain: domain::Name, domain: domain::Name,
flat_config: service::util::FlatConfig, flat_config: service::util::FlatConfig,
target_a: net::Ipv4Addr, dns_records: &'a [service::ConfigDNSRecord],
challenge_token: String, 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( self.render_page(
"/domain_init.html", "/domain_init.html",
Response { Response {
domain: args.domain, domain: args.domain,
flat_config: config.into(), flat_config: config.into(),
target_a: target_a, dns_records: &self.config.dns_records,
challenge_token: config_hash, challenge_token: config_hash,
}, },
) )

View File

@ -1,23 +1,9 @@
<h2>Configure DNS</h2> <h2>Configure DNS</h2>
<p>Next you will need to configure your DNS server to point to Domani. There <p>This step requires a passphrase that has been given to you by the
are two entries you will need to add:</p> administrator of the Domani server:</p>
<ul> <form method="GET" action="/domain_sync.html" id="syncForm">
<li>
A <code>A {{ data.domain }}</code> entry with the value
<code>{{ data.target_a }}</code>
</li>
<li>
A <code>TXT _domani_challenge.{{ data.domain }}</code> entry with the value
<code>{{ data.challenge_token }}</code>
</li>
</ul>
<p>Once complete, you can hit the following button to check your configuration
and set up your domain.</p>
<form method="GET" action="/domain_sync.html">
<input name="domain" type="hidden" value="{{ data.domain }}" /> <input name="domain" type="hidden" value="{{ data.domain }}" />
{{ #each data.flat_config }} {{ #each data.flat_config }}
<input name="{{ @key }}" type="hidden" value="{{ this }}" /> <input name="{{ @key }}" type="hidden" value="{{ this }}" />
@ -25,10 +11,50 @@ and set up your domain.</p>
<fieldset> <fieldset>
<label> <label>
Passphrase (required during closed-beta): Passphrase:
<input name="passphrase" placeholder="shhhh" required /> <input name="passphrase" placeholder="shhhh" required />
</label> </label>
</fieldset> </fieldset>
<input type="submit" value="Next" />
</form> </form>
<p>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:</p>
<table>
<tr>
<th>Type</th>
<th>Domain</th>
<th>Value</th>
</tr>
<tr>
<td>TXT</td>
<td>_domani_challenge.{{ data.domain }}</td>
<td>{{ data.challenge_token }}</td>
</tr>
</table>
<p>The second entry ensures that other users find the Domani server when they
query for your domain name. It can be <strong>one or more of</strong>:</p>
<table>
<tr>
<th>Type</th>
<th>Domain</th>
<th>Value</th>
</tr>
{{ #each data.dns_records }}
<tr>
<td>{{ this.type }}</td>
<td>{{ lookup ../data "domain" }}</td>
<td>{{ this.addr }}</td>
</tr>
{{ /each }}
</table>
<p>Once both entries are installed, you can hit the following button to check
your configuration and set up your domain.</p>
<input type="submit" value="Next" form="syncForm" />