Implement support for AAAA records

main
Brian Picciano 12 months ago
parent 7d64f44dab
commit 2693e0eac2
  1. 2
      .dev-config.yml
  2. 7
      README.md
  3. 30
      src/domain/checker.rs
  4. 4
      src/main.rs
  5. 6
      src/service.rs
  6. 17
      src/service/http.rs
  7. 66
      src/service/http/tpl/domain_init.html

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

@ -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)

@ -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<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(
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<bool, unexpected::Error> {
match self {
Self::A(addr) => Self::check_a(client, domain, &addr).await,
Self::AAAA(addr) => Self::check_aaaa(client, domain, &addr).await,
}
}
}

@ -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(),

@ -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<ConfigDNSRecord> for domain::checker::DNSRecord {
fn from(r: ConfigDNSRecord) -> Self {
match r {
ConfigDNSRecord::A { addr } => Self::A(addr),
ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr),
}
}
}

@ -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<Body> {
#[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,
},
)

@ -1,23 +1,9 @@
<h2>Configure DNS</h2>
<p>Next you will need to configure your DNS server to point to Domani. There
are two entries you will need to add:</p>
<ul>
<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">
<p>This step requires a passphrase that has been given to you by the
administrator of the Domani server:</p>
<form method="GET" action="/domain_sync.html" id="syncForm">
<input name="domain" type="hidden" value="{{ data.domain }}" />
{{ #each data.flat_config }}
<input name="{{ @key }}" type="hidden" value="{{ this }}" />
@ -25,10 +11,50 @@ and set up your domain.</p>
<fieldset>
<label>
Passphrase (required during closed-beta):
Passphrase:
<input name="passphrase" placeholder="shhhh" required />
</label>
</fieldset>
<input type="submit" value="Next" />
</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" />

Loading…
Cancel
Save