Made dns more useful

This commit is contained in:
Slatian 2023-02-24 20:17:25 +01:00
parent b900ec3b1c
commit 42fd9c0bca
4 changed files with 254 additions and 96 deletions

View File

@ -5,13 +5,17 @@
* It does not aim to be reusable for any other purpose, * It does not aim to be reusable for any other purpose,
* the trust_dns_resolver library already does that. * the trust_dns_resolver library already does that.
*/ */
use trust_dns_proto::op::response_code::ResponseCode;
use trust_dns_proto::rr::{ use trust_dns_proto::rr::{
record_type::RecordType,
RData, RData,
record_type::RecordType,
}; };
use trust_dns_resolver::{ use trust_dns_resolver::{
error::ResolveError,
error::ResolveErrorKind,
lookup::Lookup,
TokioAsyncResolver, TokioAsyncResolver,
error::*,
}; };
use std::net::IpAddr; use std::net::IpAddr;
@ -21,12 +25,20 @@ use std::net::IpAddr;
#[derive(serde::Deserialize, serde::Serialize, Default, Clone)] #[derive(serde::Deserialize, serde::Serialize, Default, Clone)]
pub struct DnsLookupResult { pub struct DnsLookupResult {
a: Vec<IpAddr>, a: Option<Vec<IpAddr>>,
aaaa: Vec<IpAddr>, aaaa: Option<Vec<IpAddr>>,
cname: Vec<String>, aname: Option<Vec<String>>,
mx: Vec<MxRecord>, cname: Option<Vec<String>>,
ns: Vec<String>, mx: Option<Vec<MxRecord>>,
soa: Vec<SoaRecord>, ns: Option<Vec<String>>,
soa: Option<Vec<SoaRecord>>,
txt: Option<Vec<String>>,
srv: Option<Vec<SrvRecord>>,
caa: Option<Vec<String>>,
other_error: bool,
dns_error: bool,
nxdomain: bool,
timeout: bool,
} }
#[derive(serde::Deserialize, serde::Serialize, Clone)] #[derive(serde::Deserialize, serde::Serialize, Clone)]
@ -46,6 +58,14 @@ pub struct SoaRecord {
minimum: u32, minimum: u32,
} }
#[derive(serde::Deserialize, serde::Serialize, Clone)]
pub struct SrvRecord {
priority: u16,
weight: u16,
port: u16,
target: String,
}
/* Lookup Functions*/ /* Lookup Functions*/
pub async fn reverse_lookup( pub async fn reverse_lookup(
@ -75,6 +95,114 @@ pub async fn reverse_lookup(
} }
} }
pub fn opush<T>(opt_vec: &mut Option<Vec<T>>, data: T) {
if opt_vec.is_none() {
*opt_vec = Some(Default::default());
}
match opt_vec {
Some(vec) => vec.push(data),
None => {},
}
}
pub fn set_default_if_none<T>(opt_vec: &mut Option<Vec<T>>) {
match opt_vec {
Some(_) => {},
None => {
*opt_vec = Some(Default::default());
}
}
}
pub fn add_record_to_lookup_result(result: &mut DnsLookupResult, record: &RData){
match record {
RData::AAAA(address) => opush(&mut result.aaaa, std::net::IpAddr::V6(*address)),
RData::ANAME(aname) => opush(&mut result.aname, aname.to_string()),
RData::A(address) => opush(&mut result.a, std::net::IpAddr::V4(*address)),
RData::CAA(caa) => opush(&mut result.caa, caa.to_string()),
RData::CNAME(cname) => opush(&mut result.cname, cname.to_string()),
RData::MX(mx) => opush(&mut result.mx, MxRecord{
preference: mx.preference(),
exchange: mx.exchange().to_string(),
}),
RData::NS(ns) => opush(&mut result.ns, ns.to_string()),
RData::SOA(soa) => opush(&mut result.soa, SoaRecord{
mname: soa.mname().to_string(),
rname: soa.rname().to_string(),
serial: soa.serial(),
refresh: soa.refresh(),
retry: soa.retry(),
expire: soa.expire(),
minimum: soa.minimum(),
}),
RData::SRV(srv) => opush(&mut result.srv, SrvRecord{
priority: srv.priority(),
weight: srv.weight(),
port: srv.port(),
target: srv.target().to_string(),
}),
RData::TXT(txt) => {
for text in txt.txt_data().iter() {
opush(
&mut result.txt,
String::from_utf8_lossy(text).into_owned()
);
}
},
_ => { println!("Tried to add an unkown DNS record to results: {record}"); },
}
}
pub fn integrate_lookup_result(dig_result: &mut DnsLookupResult, lookup_result: Result<Lookup, ResolveError>) {
match lookup_result {
Ok(lookup) => {
match lookup.query().query_type() {
RecordType::AAAA => set_default_if_none(&mut dig_result.aaaa),
RecordType::ANAME => set_default_if_none(&mut dig_result.aname),
RecordType::A => set_default_if_none(&mut dig_result.a),
RecordType::CAA => set_default_if_none(&mut dig_result.caa),
RecordType::CNAME => set_default_if_none(&mut dig_result.cname),
RecordType::MX => set_default_if_none(&mut dig_result.mx),
RecordType::NS => set_default_if_none(&mut dig_result.ns),
RecordType::SOA => set_default_if_none(&mut dig_result.soa),
RecordType::SRV => set_default_if_none(&mut dig_result.srv),
RecordType::TXT => set_default_if_none(&mut dig_result.txt),
_ => { /* This should not happen */ },
};
for record in lookup.iter() {
add_record_to_lookup_result(dig_result, record);
}
},
Err(e) => {
match e.kind() {
ResolveErrorKind::Message(..) |
ResolveErrorKind::Msg(..) |
ResolveErrorKind::NoConnections |
ResolveErrorKind::Io(..) |
ResolveErrorKind::Proto(..) => {
dig_result.other_error = true;
println!("There was an error while doing a DNS Lookup: {e}");
},
ResolveErrorKind::Timeout => {
dig_result.timeout = true;
println!("There was a timeout while doing a DNS Lookup.");
},
ResolveErrorKind::NoRecordsFound{response_code, ..} => {
match response_code {
ResponseCode::NXDomain => dig_result.nxdomain = true,
ResponseCode::NoError => {},
_ => {
println!("The DNS Server returned an error while doing a DNS Lookup: {response_code}");
dig_result.dns_error = true;
},
}
}
_ => { /*Ignore for now*/ },
}
}
}
}
// This function takes a resolver, a domain name and returns a DnsLookupResult. // This function takes a resolver, a domain name and returns a DnsLookupResult.
// If do_full_lookup is false only the A and AAAA (CNAMEs planned for the future) // If do_full_lookup is false only the A and AAAA (CNAMEs planned for the future)
// records will be fetched. // records will be fetched.
@ -83,97 +211,34 @@ pub async fn lookup(
name: &String, name: &String,
do_full_lookup: bool, do_full_lookup: bool,
) -> DnsLookupResult { ) -> DnsLookupResult {
let ipv4_lookup_res = resolver.ipv4_lookup(name); let ipv4_lookup_res = resolver.lookup(name, RecordType::A);
let ipv6_lookup_res = resolver.ipv6_lookup(name); let ipv6_lookup_res = resolver.lookup(name, RecordType::AAAA);
let cname_lookup_res = resolver.lookup(name, RecordType::CNAME); let cname_lookup_res = resolver.lookup(name, RecordType::CNAME);
let aname_lookup_res = resolver.lookup(name, RecordType::ANAME);
// initlize an empty lookup result // initlize an empty lookup result
let mut dig_result: DnsLookupResult = Default::default(); let mut dig_result: DnsLookupResult = Default::default();
match ipv4_lookup_res.await { integrate_lookup_result(&mut dig_result, ipv4_lookup_res.await);
Ok(lookup) => { integrate_lookup_result(&mut dig_result, ipv6_lookup_res.await);
for address in lookup { integrate_lookup_result(&mut dig_result, cname_lookup_res.await);
dig_result.a.push(std::net::IpAddr::V4(address)); integrate_lookup_result(&mut dig_result, aname_lookup_res.await);
}
}
Err(e) => {
println!("There was an error while looking A up {name}: {e}");
}
}
match ipv6_lookup_res.await { //Don't do an extented lookup if the domain seemingly doesn't exist
Ok(lookup) => { if do_full_lookup && !dig_result.nxdomain {
for address in lookup { let mx_lookup_res = resolver.lookup(name, RecordType::MX);
dig_result.aaaa.push(std::net::IpAddr::V6(address)); let ns_lookup_res = resolver.lookup(name, RecordType::NS);
} let soa_lookup_res = resolver.lookup(name, RecordType::SOA);
} let caa_lookup_res = resolver.lookup(name, RecordType::CAA);
Err(e) => { let srv_lookup_res = resolver.lookup(name, RecordType::SRV);
println!("There was an error while looking AAAA up {name}: {e}"); let txt_lookup_res = resolver.lookup(name, RecordType::TXT);
}
}
match cname_lookup_res.await { integrate_lookup_result(&mut dig_result, mx_lookup_res.await);
Ok(lookup) => { integrate_lookup_result(&mut dig_result, ns_lookup_res.await);
for record in lookup { integrate_lookup_result(&mut dig_result, soa_lookup_res.await);
match record { integrate_lookup_result(&mut dig_result, caa_lookup_res.await);
RData::CNAME(cname) => dig_result.cname.push(cname.to_string()), integrate_lookup_result(&mut dig_result, srv_lookup_res.await);
_ => {}, integrate_lookup_result(&mut dig_result, txt_lookup_res.await);
}
}
}
Err(e) => {
println!("There was an error while looking CNAME up {name}: {e}");
}
}
if do_full_lookup {
let mx_lookup_res = resolver.mx_lookup(name);
let ns_lookup_res = resolver.ns_lookup(name);
let soa_lookup_res = resolver.soa_lookup(name);
match mx_lookup_res.await {
Ok(lookup) => {
for mx in lookup {
dig_result.mx.push(MxRecord{
preference: mx.preference(),
exchange: mx.exchange().to_string(),
});
}
}
Err(e) => {
println!("There was an error while looking MX up {name}: {e}");
}
}
match ns_lookup_res.await {
Ok(lookup) => {
for ns in lookup {
dig_result.ns.push(ns.to_string());
}
}
Err(e) => {
println!("There was an error while looking NS up {name}: {e}");
}
}
match soa_lookup_res.await {
Ok(lookup) => {
for soa in lookup {
dig_result.soa.push(SoaRecord{
mname: soa.mname().to_string(),
rname: soa.rname().to_string(),
serial: soa.serial(),
refresh: soa.refresh(),
retry: soa.retry(),
expire: soa.expire(),
minimum: soa.minimum(),
});
}
}
Err(e) => {
println!("There was an error while looking MX up {name}: {e}");
}
}
} }

View File

@ -26,8 +26,9 @@
<nav> <nav>
<a href="{{ extra.base_url }}" class="sitename">{{extra.site_name|default(value="echoip")}}</a> <a href="{{ extra.base_url }}" class="sitename">{{extra.site_name|default(value="echoip")}}</a>
<form class="search" method="GET" action="{{ extra.base_url }}"> <form class="search" method="GET" action="{{ extra.base_url }}">
<input type="text" name="query" autocomplete="on" maxlength="260" <input type="search" name="query" autocomplete="on" maxlength="260"
title="Search for an IP-Adress, Domain-Name, or ASN." title="Search for an IP-Adress, Domain-Name, or ASN."
placeholder="1.2.3.4, 2001::1:2:3:4, example.org, AS1234"
value="{% if view == "dig" %}{{ data.query }}{% elif view == "ip" %}{{ data.result.address }}{% elif view == "asn"%}AS{{ data.asn }}{% endif %}"/> value="{% if view == "dig" %}{{ data.query }}{% elif view == "ip" %}{{ data.result.address }}{% elif view == "asn"%}AS{{ data.asn }}{% endif %}"/>
<input type="submit" value="Query"/> <input type="submit" value="Query"/>
</form> </form>
@ -39,5 +40,10 @@
<p>If you see this the templating is broken. Greetings from the base template.</p> <p>If you see this the templating is broken. Greetings from the base template.</p>
{% endblock %} {% endblock %}
</main> </main>
<footer>
<p>You can find the <a href="https://codeberg.org/slatian/service.echoip-slatecave">echoip-slatecave sourcecode on Codeberg</a>. If you found a bug or have an idea, feature, etc. please get in touch (I also accept E-Mails!).</p>
<!-- If you made your own template, link to it here -->
<p>This service works in its current form because nobody is abusing it, please keep that in mind. If you want to do frequent automated requests please host your own instace. Thank you for being awesome!</p>
</footer>
</body> </body>
</html> </html>

View File

@ -25,6 +25,40 @@
{% set r = data.result.records %} {% set r = data.result.records %}
<section> <section>
<h2>DNS Records</h2> <h2>DNS Records</h2>
{% if r.nxdomain %}
<p class="error box">Our DNS-Server claims that this domain doesn't exist, you shouldn't see any results below.</p>
{% elif r.timeout %}
<p class="error box">There was at least one timeout error while resolving this domain, the results below are incomplete.</p>
{% elif r.other_error %}
<p class="error box">An error happened while resolving this name, the results below are incomplete. There was probably some IO issue, the error has been written to the log to help with debugging.</p>
{% endif %}
{% if r.dns_error %}
<p class="error box">The DNS-Server returned an error code that is not NXDomain, the results are probably incomplete. To help with debugging this has been written to the log.</p>
{% endif %}
{% if r.cname %}
<p>This domain has a cname set, this means its contents are full replaced by the linked record.</p>
<p class="button-paragraph">{{ helper::dig(extra=extra, name=r.cname[1]) }}</p>
</p>Usually you get the <code>A</code> and <code>AAAA</code> records for the linked record to avoid uneccessary requests. If anything else resolves, that is a violation of the DNS specification.</p>
{% if r.cname | length > 1 %}
<p>This domain resolves to multiple <code>CNAME</code>s, this is not allowed by the DNS specification!</p>
{% endif %}
{% endif %}
{% if r.aname %}
<p>This domain has one or multiple <code>ANAME</code> (or <code>ALIAS</code>) records set that the DNS server communicates:</p>
<ul class="link-list">
{% for aname in r.aname%}
<li>{{ helper::dig(extra=extra, name=aname) }}</li>
{% endfor %}
</ul>
{% endif %}
{% if r.a %} {% if r.a %}
<p id="a"><code>A</code> (IPv4) records:</p> <p id="a"><code>A</code> (IPv4) records:</p>
<ul class="link-list"> <ul class="link-list">
@ -52,7 +86,7 @@
{% if r.mx %} {% if r.mx %}
<p id="mx"><code>MX</code> (Mail Exchange) records:</p> <p id="mx"><code>MX</code> (Mail Exchange) records:</p>
<ul class="link-list"> <ul class="link-list">
{% for mx in r.mx%} {% for mx in r.mx | sort(attribute="preference") | reverse %}
<li>{{ helper::dig(extra=extra, name=mx.exchange, fqdn=true, prefix=mx.preference) }}</li> <li>{{ helper::dig(extra=extra, name=mx.exchange, fqdn=true, prefix=mx.preference) }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -60,10 +94,55 @@
<p>No <code>MX</code> (Mail Exchange) records.</p> <p>No <code>MX</code> (Mail Exchange) records.</p>
{% endif %} {% endif %}
{% if r.caa %}
<p id="caa"><code>CAA</code> (<a targt="_blank" href="https://de.wikipedia.org/wiki/DNS_Certification_Authority_Authorization">Certification Authority Authorization</a>) records:</p>
<ul>
{% for caa in r.caa %}
<li><code>{{caa}}</code></li>
{% endfor %}
</ul>
{% else %}
<p id="caa">No <code>CAA</code> (<a target="_blank" href="https://de.wikipedia.org/wiki/DNS_Certification_Authority_Authorization">Certification Authority Authorization</a>) records.</p>
{% endif %}
{% if r.txt %}
<p id="txt"><code>TXT</code> records:</p>
<ul>
{% for txt in r.txt %}
<li><code>{{txt}}</code></li>
{% endfor %}
</ul>
{% else %}
<p id="txt">No <code>TXT</code> records.</p>
{% endif %}
{% if r.srv %}
<p id="srv"><code>SRV</code> records:</p>
<ul class="link-list">
{% for srv in r.srv %}
<dl class="box">
<dt>Priority</dt>
<dd>{{srv.priority}}</dd>
<dt>Weight</dt>
<dd>{{srv.weight}}</dd>
<dt>Port</dt>
<dd>{{srv.port}}</dd>
<dt>Target</dt>
<dd>{{ helper::dig(extra=extra,name=srv.target) }}</dd>
</dl>
{% endfor %}
</ul>
{% else %}
<p id="srv">No <code>SRV</code> records.</p>
<p><code>SRV</code> or Service records usually live on their own subdomains like {{ helper::dig(extra=extra, name="_xmpp-client._tcp."~data.query) }}.
{% endif %}
{% else %}{# if data.partial_lookup #} {% else %}{# if data.partial_lookup #}
<p>To save resources the above is only a partial lookup.</p> <p>To save resources the above is only a partial lookup.</p>
<p class="button-paragraph"><a href="{{ helper::dig_link(extra=extra, name=data.query) }}">Extended DNS Lookup for <code>{{ data.query }}</code>.</a></p> <p class="button-paragraph"><a href="{{ helper::dig_link(extra=extra, name=data.query) }}">Extended DNS Lookup for <code>{{ data.query }}</code>.</a></p>
{% endif %} {% endif %}
</section> </section>
<section> <section>
@ -71,5 +150,13 @@
{{ links::domain_name_links(name=idn.idn|default(value=data.query))}} {{ links::domain_name_links(name=idn.idn|default(value=data.query))}}
</section> </section>
<section>
<h2>Programatic Lookup</h2>
<p>If you want to look up this information in another program the short answer is <b>don't, look up the names using your local DNS!</b></p>
<p>On most systems on the commandline you have commands like <code>host</code> and <code>dig</code> even when not present you can probably use <code>ping</code> as a workaround as it resolves the name and gives you the IP-Address it is pinging.</p>
<h3>Why queryting this service is still useful</h3>
<p>This service most probably doesn't share its cache with your local resolver, this way you have a way to see if your DNS-change had the effect it should have.</p>
<p>It may also be useful for debugging other dns problems or to get around a local resolver that is lying to you because your ISP is a <i>something</i>.</p>
</section>
{% endblock %} {% endblock %}

View File

@ -20,10 +20,10 @@
<h2>Your User Agent</h2> <h2>Your User Agent</h2>
<p>The program you were using to download this page <a href="https://en.wikipedia.org/wiki/User_agent">identified itself</a> as <code>{{ data.user_agent }}</code></p> <p>The program you were using to download this page <a href="https://en.wikipedia.org/wiki/User_agent">identified itself</a> as <code>{{ data.user_agent }}</code></p>
<p>While this doesn't have to do anything with your public IP-Adress this might be useful information.</p> <p>While this doesn't have to do anything with your public IP-Adress this might be useful information.</p>
<p>You can use the <code><a href="{{ extra.base_url}}/ua">/ua</a></code> endpoint to fetch this information with any client</p> <p>You can use the <code><a href="{{ extra.base_url}}/ua">/ua</a></code> endpoint to fetch this information with any client.</p>
</section> </section>
<section> <section>
<h2>Did you know?</h2> <h2>Did you know?</h2>
<p>If you share this site and the Link gets a preview. The IP-Address after the dash is the one of the machine that generated that preview</p> <p>If you share this site and the Link gets a preview. The IP-Address after the dash is the one of the machine that generated that preview.</p>
</section> </section>
{% endblock %} {% endblock %}