Implement support for AAAA records
This commit is contained in:
parent
7d64f44dab
commit
8600c1050e
@ -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,6 @@ Within the shell which opens you can do `cargo run` to start a local instance.
|
||||
|
||||
## Roadmap
|
||||
|
||||
* Support for AAAA and 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>
|
||||
<p>This step requires a passphrase that has been given to you by the
|
||||
administrator of the Domani server:</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">
|
||||
<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…
Reference in New Issue
Block a user