mirror of
https://github.com/aljazceru/pkdns.git
synced 2025-12-17 05:54:21 +01:00
initial commit
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Mac pollution
|
||||||
|
.DS_STORE
|
||||||
1928
Cargo.lock
generated
Normal file
1928
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "pkdns"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ctrlc = "3.4.2"
|
||||||
|
simple-dns = "0.6.0"
|
||||||
|
pknames_core = "0.1.0"
|
||||||
|
pkarr = "1.0.1"
|
||||||
|
zbase32 = "0.1.2"
|
||||||
|
ttl_cache = "0.5.1"
|
||||||
|
clap = "4.4.18"
|
||||||
|
any-dns = "0.1.1"
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (Severin Alexander Bühler) 2023
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# pkdns
|
||||||
|
|
||||||
|
DNS server resolving [pkarr](https://github.com/nuhvi/pkarr) self-sovereign domains.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Build Yourself
|
||||||
|
|
||||||
|
1. Run `cargo run`.
|
||||||
|
2. Configure you system to send DNS requests to `127.0.0.1:53`.
|
||||||
|
3. Test by going to [http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/](http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/).
|
||||||
|
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: pkdns [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f, --forward <forward> ICANN fallback DNS server. IP:Port [default: 192.168.1.1:53]
|
||||||
|
-s, --socket <socket> Socket the server should listen on. IP:Port [default: 0.0.0.0:53]
|
||||||
|
-v, --verbose Show verbose output.
|
||||||
|
--no-cache Disable DHT packet caching.
|
||||||
|
--threads <threads> Number of threads to process dns queries. [default: 4]
|
||||||
|
-d, --directory <directory> pknames source directory. [default: ~/.pknames]
|
||||||
|
-h, --help Print help
|
||||||
|
-V, --version Print version
|
||||||
|
```
|
||||||
36
build.sh
Executable file
36
build.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo Build OSX amd64
|
||||||
|
cargo build --release --package=pkdns
|
||||||
|
echo Build Linux amd64
|
||||||
|
cargo build --release --package=pkdns --target=x86_64-unknown-linux-gnu
|
||||||
|
echo Build Windows amd64
|
||||||
|
cargo build --release --package=pkdns --target=x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo Build packets
|
||||||
|
rm -rf target/github-release
|
||||||
|
cd target
|
||||||
|
mkdir github-release
|
||||||
|
|
||||||
|
echo Tar osx
|
||||||
|
mkdir github-release/pknames-osx-amd64
|
||||||
|
cp release/pkdns github-release/pknames-osx-amd64
|
||||||
|
cd github-release && tar -czf pknames-osx-amd64.tar.gz pknames-osx-amd64 && cd ..
|
||||||
|
rm -rf github-release/pknames-osx-amd64
|
||||||
|
|
||||||
|
echo Tar linux
|
||||||
|
mkdir github-release/pknames-linux-amd64
|
||||||
|
cp x86_64-unknown-linux-gnu/release/pkdns github-release/pknames-linux-amd64
|
||||||
|
cd github-release && tar -czf pknames-linux-amd64.tar.gz pknames-linux-amd64 && cd ..
|
||||||
|
rm -rf github-release/pknames-linux-amd64
|
||||||
|
|
||||||
|
echo Tar Windows
|
||||||
|
mkdir github-release/pknames-windows-amd64
|
||||||
|
cp x86_64-pc-windows-gnu/release/pkdns.exe github-release/pknames-windows-amd64
|
||||||
|
cd github-release && tar -czf pknames-windows-amd64.tar.gz pknames-windows-amd64 && cd ..
|
||||||
|
rm -rf github-release/pknames-windows-amd64
|
||||||
|
|
||||||
|
echo
|
||||||
|
cd ..
|
||||||
|
tree target/github-release
|
||||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
max_width = 120
|
||||||
152
src/main.rs
Normal file
152
src/main.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use any_dns::{Builder, CustomHandler};
|
||||||
|
use ctrlc;
|
||||||
|
use pkarr::dns::Packet;
|
||||||
|
use pknames_resolver::PknamesResolver;
|
||||||
|
use std::{error::Error, net::SocketAddr, sync::mpsc::channel, time::Instant};
|
||||||
|
|
||||||
|
mod pkarr_cache;
|
||||||
|
mod pkarr_resolver;
|
||||||
|
mod pknames_resolver;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MyHandler {
|
||||||
|
pub pkarr: PknamesResolver,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyHandler {
|
||||||
|
pub fn new(max_cache_ttl: u64, config_dir_path: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
pkarr: PknamesResolver::new(max_cache_ttl, config_dir_path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomHandler for MyHandler {
|
||||||
|
fn lookup(&mut self, query: &Vec<u8>) -> std::prelude::v1::Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let start = Instant::now();
|
||||||
|
let result = self.pkarr.resolve(query);
|
||||||
|
if result.is_ok() {
|
||||||
|
let query = Packet::parse(&query).unwrap();
|
||||||
|
println!(
|
||||||
|
"Resolved {:?} within {}ms",
|
||||||
|
query.questions.first().unwrap(),
|
||||||
|
start.elapsed().as_millis()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_on_ctrl_c() {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
|
||||||
|
.expect("Error setting Ctrl-C handler");
|
||||||
|
rx.recv().expect("Could not receive from channel.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
let cmd = clap::Command::new("pkdns")
|
||||||
|
.about("A DNS server for pkarr self-sovereign domains.")
|
||||||
|
.version(VERSION)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("forward")
|
||||||
|
.short('f')
|
||||||
|
.long("forward")
|
||||||
|
.required(false)
|
||||||
|
.default_value("192.168.1.1:53")
|
||||||
|
.help("ICANN fallback DNS server. IP:Port"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("socket")
|
||||||
|
.short('s')
|
||||||
|
.long("socket")
|
||||||
|
.required(false)
|
||||||
|
.default_value("0.0.0.0:53")
|
||||||
|
.help("Socket the server should listen on. IP:Port"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("verbose")
|
||||||
|
.short('v')
|
||||||
|
.long("verbose")
|
||||||
|
.required(false)
|
||||||
|
.num_args(0)
|
||||||
|
.help("Show verbose output."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("no-cache")
|
||||||
|
.long("no-cache")
|
||||||
|
.required(false)
|
||||||
|
.num_args(0)
|
||||||
|
.help("Disable DHT packet caching."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("threads")
|
||||||
|
.long("threads")
|
||||||
|
.required(false)
|
||||||
|
.default_value("4")
|
||||||
|
.help("Number of threads to process dns queries."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
clap::Arg::new("directory")
|
||||||
|
.short('d')
|
||||||
|
.long("directory")
|
||||||
|
.required(false)
|
||||||
|
.help("pknames source directory.")
|
||||||
|
.default_value("~/.pknames"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let matches = cmd.get_matches();
|
||||||
|
let verbose: bool = *matches.get_one("verbose").unwrap();
|
||||||
|
let no_cache: bool = *matches.get_one("no-cache").unwrap();
|
||||||
|
let directory: &String = matches.get_one("directory").unwrap();
|
||||||
|
let threads: &String = matches.get_one("threads").unwrap();
|
||||||
|
let threads: u8 = threads.parse().expect("threads should be valid positive integer.");
|
||||||
|
let forward: &String = matches.get_one("forward").unwrap();
|
||||||
|
let forward: SocketAddr = forward.parse().expect("forward should be valid IP:Port combination.");
|
||||||
|
let socket: &String = matches.get_one("socket").unwrap();
|
||||||
|
let socket: SocketAddr = socket.parse().expect("socket should be valid IP:Port combination.");
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
println!("Verbose mode");
|
||||||
|
}
|
||||||
|
if no_cache {
|
||||||
|
println!("Disabled DHT cache")
|
||||||
|
}
|
||||||
|
if threads != 4 {
|
||||||
|
println!("Use {} threads", threads);
|
||||||
|
}
|
||||||
|
if directory != "~/.pknames" {
|
||||||
|
println!("Use pknames directory {}", directory);
|
||||||
|
}
|
||||||
|
if forward.to_string() != "192.168.1.1:53" {
|
||||||
|
println!("Forward ICANN queries to {}", forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let max_ttl = if no_cache {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
60*60
|
||||||
|
};
|
||||||
|
|
||||||
|
let anydns = Builder::new()
|
||||||
|
.handler(MyHandler::new(max_ttl, directory))
|
||||||
|
.threads(threads)
|
||||||
|
.verbose(verbose)
|
||||||
|
.icann_resolver(forward)
|
||||||
|
.listen(socket)
|
||||||
|
.build();
|
||||||
|
println!("Listening on {}. Waiting for Ctrl-C...", socket);
|
||||||
|
|
||||||
|
wait_on_ctrl_c();
|
||||||
|
println!("Got it! Exiting...");
|
||||||
|
anydns.join();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
45
src/pkarr_cache.rs
Normal file
45
src/pkarr_cache.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use pkarr::{dns::Packet, PublicKey};
|
||||||
|
use ttl_cache::TtlCache;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pkarr record ttl cache
|
||||||
|
*/
|
||||||
|
pub struct PkarrPacketTtlCache{
|
||||||
|
cache: TtlCache<String, Vec<u8>>,
|
||||||
|
max_cache_ttl: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PkarrPacketTtlCache {
|
||||||
|
pub fn new(max_cache_ttl: u64) -> Self {
|
||||||
|
PkarrPacketTtlCache{
|
||||||
|
cache: TtlCache::new(100),
|
||||||
|
max_cache_ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds packet and caches it for the ttl the least long lived answer is valid for.
|
||||||
|
*/
|
||||||
|
pub fn add(&mut self, pubkey: PublicKey, reply: Vec<u8>) {
|
||||||
|
let default_ttl = 1200;
|
||||||
|
let packet = Packet::parse(&reply).unwrap();
|
||||||
|
let min_ttl = packet.answers.iter().map(|answer| answer.ttl).min().unwrap_or(default_ttl) as u64;
|
||||||
|
|
||||||
|
let ttl = 60.max(min_ttl); // At least 1min
|
||||||
|
let ttl = ttl.min(self.max_cache_ttl);
|
||||||
|
let ttl = Duration::from_secs(ttl as u64);
|
||||||
|
|
||||||
|
self.cache.insert(pubkey.to_z32(), reply, ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, pubkey: &PublicKey) -> Option<Vec<u8>> {
|
||||||
|
let z32 = pubkey.to_z32();
|
||||||
|
self.cache.get(&z32).map(|value| value.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
236
src/pkarr_resolver.rs
Normal file
236
src/pkarr_resolver.rs
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
use std::{error::Error, sync::{Arc, Mutex}};
|
||||||
|
|
||||||
|
use pkarr::{
|
||||||
|
dns::{Packet, ResourceRecord},
|
||||||
|
PkarrClient, PublicKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::pkarr_cache::PkarrPacketTtlCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pkarr resolver with cache.
|
||||||
|
*/
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PkarrResolver {
|
||||||
|
client: PkarrClient,
|
||||||
|
cache: Arc<Mutex<PkarrPacketTtlCache>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PkarrResolver {
|
||||||
|
pub fn new(max_cache_ttl: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
client: PkarrClient::new(),
|
||||||
|
cache: Arc::new(Mutex::new(PkarrPacketTtlCache::new(max_cache_ttl))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_pkarr_uri(uri: &str) -> Option<PublicKey> {
|
||||||
|
let decoded = zbase32::decode_full_bytes_str(uri);
|
||||||
|
if decoded.is_err() {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let decoded = decoded.unwrap();
|
||||||
|
if decoded.len() != 32 {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let trying: Result<PublicKey, _> = uri.try_into();
|
||||||
|
trying.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_pubkey_respect_cache(&mut self, pubkey: &PublicKey) -> Option<Vec<u8>> {
|
||||||
|
let mut cache = self.cache.lock().unwrap();
|
||||||
|
let cached_opt = cache.get(pubkey);
|
||||||
|
if cached_opt.is_some() {
|
||||||
|
let reply_bytes = cached_opt.unwrap();
|
||||||
|
return Some(reply_bytes)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let packet_option = self.client.resolve(pubkey.clone());
|
||||||
|
if packet_option.is_none() {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let reply_bytes = packet_option.unwrap().packet().build_bytes_vec().unwrap();
|
||||||
|
cache.add(pubkey.clone(), reply_bytes);
|
||||||
|
cache.get(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a domain with pkarr.
|
||||||
|
*/
|
||||||
|
pub fn resolve(
|
||||||
|
&mut self,
|
||||||
|
query: &Vec<u8>
|
||||||
|
) -> std::prelude::v1::Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let request = Packet::parse(query)?;
|
||||||
|
|
||||||
|
let question_opt = request.questions.first();
|
||||||
|
if question_opt.is_none() {
|
||||||
|
return Err("Missing question".into());
|
||||||
|
}
|
||||||
|
let question = question_opt.unwrap();
|
||||||
|
let labels = question.qname.get_labels();
|
||||||
|
|
||||||
|
let raw_pubkey = labels.last().unwrap().to_string();
|
||||||
|
let parsed_option = Self::parse_pkarr_uri(&raw_pubkey);
|
||||||
|
if parsed_option.is_none() {
|
||||||
|
return Err("Invalid pkarr pubkey".into());
|
||||||
|
}
|
||||||
|
let pubkey = parsed_option.unwrap();
|
||||||
|
|
||||||
|
let packet_option = self.resolve_pubkey_respect_cache(&pubkey);
|
||||||
|
if packet_option.is_none() {
|
||||||
|
return Err("No pkarr packet found for pubkey".into());
|
||||||
|
}
|
||||||
|
let packet = packet_option.unwrap();
|
||||||
|
let packet = Packet::parse(&packet).unwrap();
|
||||||
|
|
||||||
|
let matching_records: Vec<ResourceRecord<'_>> = packet.answers.iter()
|
||||||
|
.filter(|record| record.match_qclass(question.qclass) && record.match_qtype(question.qtype) && record.name == question.qname)
|
||||||
|
.map(|record| record.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut reply = request.into_reply();
|
||||||
|
reply.answers = matching_records;
|
||||||
|
// println!("Pkarr reply {:?} with {} ms", reply, start.elapsed().as_millis());
|
||||||
|
let reply_bytes: Vec<u8> = reply.build_bytes_vec()?;
|
||||||
|
Ok(reply_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pkarr::{
|
||||||
|
dns::{Name, Packet, Question, ResourceRecord},
|
||||||
|
Keypair, SignedPacket,
|
||||||
|
};
|
||||||
|
// use simple_dns::{Name, Question, Packet};
|
||||||
|
use super::*;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
use zbase32;
|
||||||
|
|
||||||
|
fn get_test_keypair() -> Keypair {
|
||||||
|
// pk:cb7xxx6wtqr5d6yqudkt47drqswxk57dzy3h7qj3udym5puy9cso
|
||||||
|
let secret = "6kfe1u5jyqxg644eqfgk1cp4w9yjzwq51rn11ftysuo6xkpc64by";
|
||||||
|
let seed = zbase32::decode_full_bytes_str(secret).unwrap();
|
||||||
|
let slice: &[u8; 32] = &seed[0..32].try_into().unwrap();
|
||||||
|
let keypair = Keypair::from_secret_key(slice);
|
||||||
|
keypair
|
||||||
|
}
|
||||||
|
|
||||||
|
fn publish_record() {
|
||||||
|
let keypair = get_test_keypair();
|
||||||
|
// let uri = keypair.to_uri_string();
|
||||||
|
// println!("Publish packet with pubkey {}", uri);
|
||||||
|
|
||||||
|
let mut packet = Packet::new_reply(0);
|
||||||
|
let ip: Ipv4Addr = "93.184.216.34".parse().unwrap();
|
||||||
|
let record = ResourceRecord::new(
|
||||||
|
Name::new("pknames.p2p").unwrap(),
|
||||||
|
pkarr::dns::CLASS::IN,
|
||||||
|
100,
|
||||||
|
pkarr::dns::rdata::RData::A(ip.try_into().unwrap()),
|
||||||
|
);
|
||||||
|
packet.answers.push(record);
|
||||||
|
let record = ResourceRecord::new(
|
||||||
|
Name::new(".").unwrap(),
|
||||||
|
pkarr::dns::CLASS::IN,
|
||||||
|
100,
|
||||||
|
pkarr::dns::rdata::RData::A(ip.try_into().unwrap()),
|
||||||
|
);
|
||||||
|
packet.answers.push(record);
|
||||||
|
let signed_packet = SignedPacket::from_packet(&keypair, &packet).unwrap();
|
||||||
|
|
||||||
|
let client = PkarrClient::new();
|
||||||
|
let result = client.publish(&signed_packet);
|
||||||
|
result.expect("Should have published.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_domain() {
|
||||||
|
publish_record();
|
||||||
|
|
||||||
|
let keypair = get_test_keypair();
|
||||||
|
let domain = format!("pknames.p2p.{}", keypair.to_z32());
|
||||||
|
let name = Name::new(&domain).unwrap();
|
||||||
|
let mut query = Packet::new_query(0);
|
||||||
|
let question = Question::new(
|
||||||
|
name.clone(),
|
||||||
|
pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A),
|
||||||
|
pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
query.questions.push(question);
|
||||||
|
|
||||||
|
let mut resolver = PkarrResolver::new(0);
|
||||||
|
let result = resolver.resolve(&query.build_bytes_vec().unwrap());
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let reply_bytes = result.unwrap();
|
||||||
|
let reply = Packet::parse(&reply_bytes).unwrap();
|
||||||
|
assert_eq!(reply.id(), query.id());
|
||||||
|
assert_eq!(reply.answers.len(), 1);
|
||||||
|
let answer = reply.answers.first().unwrap();
|
||||||
|
assert_eq!(answer.name.to_string(), name.to_string());
|
||||||
|
assert_eq!(answer.rdata.type_code(), pkarr::dns::TYPE::A);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_pubkey() {
|
||||||
|
publish_record();
|
||||||
|
|
||||||
|
let keypair = get_test_keypair();
|
||||||
|
let domain = keypair.to_z32();
|
||||||
|
let name = Name::new(&domain).unwrap();
|
||||||
|
let mut query = Packet::new_query(0);
|
||||||
|
let question = Question::new(
|
||||||
|
name.clone(),
|
||||||
|
pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A),
|
||||||
|
pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
query.questions.push(question);
|
||||||
|
let mut resolver = PkarrResolver::new(0);
|
||||||
|
let result = resolver.resolve(&query.build_bytes_vec().unwrap());
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let reply_bytes = result.unwrap();
|
||||||
|
let reply = Packet::parse(&reply_bytes).unwrap();
|
||||||
|
assert_eq!(reply.id(), query.id());
|
||||||
|
assert_eq!(reply.answers.len(), 1);
|
||||||
|
let answer = reply.answers.first().unwrap();
|
||||||
|
assert_eq!(answer.name.to_string(), name.to_string());
|
||||||
|
assert_eq!(answer.rdata.type_code(), pkarr::dns::TYPE::A);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_invalid_pubkey() {
|
||||||
|
let domain = "invalid_pubkey";
|
||||||
|
let name = Name::new(&domain).unwrap();
|
||||||
|
let mut query = Packet::new_query(0);
|
||||||
|
let question = Question::new(
|
||||||
|
name.clone(),
|
||||||
|
pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A),
|
||||||
|
pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
query.questions.push(question);
|
||||||
|
let mut resolver = PkarrResolver::new(0);
|
||||||
|
let result = resolver.resolve(&query.build_bytes_vec().unwrap());
|
||||||
|
assert!(result.is_err());
|
||||||
|
// println!("{}", result.unwrap_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pkarr_parse() {
|
||||||
|
let domain = "cb7xxx6wtqr5d6yqudkt47drqswxk57dzy3h7qj3udym5puy9cso";
|
||||||
|
let decoded = zbase32::decode_full_bytes_str(domain);
|
||||||
|
// assert!(decoded.is_err());
|
||||||
|
let decoded = decoded.unwrap();
|
||||||
|
println!("{:?}", decoded);
|
||||||
|
if decoded.len() != 32 {
|
||||||
|
println!("wrong length");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let trying: Result<PublicKey, _> = domain.try_into();
|
||||||
|
assert!(trying.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/pknames_resolver.rs
Normal file
102
src/pknames_resolver.rs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
use pkarr::dns::{Name, Packet};
|
||||||
|
use pknames_core::resolve::resolve_standalone;
|
||||||
|
use crate::pkarr_resolver::PkarrResolver;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PknamesResolver {
|
||||||
|
pkarr: PkarrResolver,
|
||||||
|
config_dir_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PknamesResolver {
|
||||||
|
pub fn new(max_cache_ttl: u64, config_dir_path: &str) -> Self {
|
||||||
|
PknamesResolver {
|
||||||
|
pkarr: PkarrResolver::new(max_cache_ttl),
|
||||||
|
config_dir_path: config_dir_path.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a regular pknames domain into a pkarr domain.
|
||||||
|
* Example: `pknames.p2p` -> `pknames.p2p.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy`.
|
||||||
|
*/
|
||||||
|
fn predict_pknames_domain(&self, domain: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let result = resolve_standalone(&domain, &self.config_dir_path);
|
||||||
|
if result.is_err() {
|
||||||
|
return Err("Neither pkarr nor pknames domain.".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let predictions = result.unwrap();
|
||||||
|
let best_class = predictions.get_best_class().expect("Class should be available.");
|
||||||
|
|
||||||
|
let best_pubkey = best_class.pubkey.clone();
|
||||||
|
let best_pubkey = best_pubkey.replace("pk:", ""); // Just to be sure
|
||||||
|
|
||||||
|
let full_domain = format!("{}.{}", domain, best_pubkey);
|
||||||
|
Ok(full_domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn resolve(&mut self, query: &Vec<u8>) -> std::prelude::v1::Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
|
let original_query = Packet::parse(query)?;
|
||||||
|
|
||||||
|
let pkarr_result = self.pkarr.resolve(&query.clone());
|
||||||
|
if pkarr_result.is_ok() {
|
||||||
|
return pkarr_result; // It was a pkarr hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
let question = original_query.questions.first().unwrap();
|
||||||
|
let domain = question.qname.to_string();
|
||||||
|
let pkarr_domain = self.predict_pknames_domain(&domain)?;
|
||||||
|
|
||||||
|
let qname = Name::new(&pkarr_domain).unwrap();
|
||||||
|
let mut pkarr_query = original_query.clone();
|
||||||
|
pkarr_query.questions[0].qname = qname;
|
||||||
|
let pkarr_query = pkarr_query.build_bytes_vec().unwrap();
|
||||||
|
let pkarr_reply = self.pkarr.resolve(&pkarr_query)?;
|
||||||
|
let pkarr_reply = Packet::parse(&pkarr_reply).unwrap();
|
||||||
|
|
||||||
|
let mut reply = original_query.clone().into_reply();
|
||||||
|
for answer in pkarr_reply.answers.iter() {
|
||||||
|
let mut answer = answer.clone();
|
||||||
|
answer.name = question.qname.clone();
|
||||||
|
reply.answers.push(answer);
|
||||||
|
};
|
||||||
|
Ok(reply.build_bytes_vec().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pkarr::dns::{Name, Packet, Question};
|
||||||
|
|
||||||
|
use super::PknamesResolver;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_pubkey() {
|
||||||
|
let mut pknames = PknamesResolver::new(1, "~/.pknames");
|
||||||
|
|
||||||
|
let mut query = Packet::new_query(0);
|
||||||
|
let name = Name::new("pknames.p2p").unwrap();
|
||||||
|
let question = Question::new(name, pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A), pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN), false);
|
||||||
|
query.questions.push(question);
|
||||||
|
let query_bytes = query.build_bytes_vec().unwrap();
|
||||||
|
|
||||||
|
let result = pknames.resolve(&query_bytes);
|
||||||
|
if result.is_err() {
|
||||||
|
eprintln!("{:?}", result.unwrap_err());
|
||||||
|
assert!(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let reply = result.unwrap();
|
||||||
|
let reply = Packet::parse(&reply).unwrap();
|
||||||
|
println!("{:?}", reply);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user