Full Code of pubky/pkdns for AI

master 221fe940116b cached
70 files
209.5 KB
55.4k tokens
369 symbols
1 requests
Download .txt
Showing preview only (227K chars total). Download the full file or copy to clipboard to get everything.
Repository: pubky/pkdns
Branch: master
Commit: 221fe940116b
Files: 70
Total size: 209.5 KB

Directory structure:
gitextract__l9rdkfn/

├── .github/
│   ├── actions/
│   │   └── setup/
│   │       └── action.yaml
│   └── workflows/
│       ├── build-artifacts.yml
│       └── rust.yaml
├── .gitignore
├── .scripts/
│   ├── build-artifacts.sh
│   └── build_docker.sh
├── Cargo.toml
├── Cross.toml
├── Dockerfile
├── LICENSE
├── README.md
├── cli/
│   ├── Cargo.toml
│   ├── sample/
│   │   ├── README.md
│   │   ├── pkarr.zone
│   │   └── seed.txt
│   └── src/
│       ├── cli.rs
│       ├── commands/
│       │   ├── generate.rs
│       │   ├── mod.rs
│       │   ├── publickey.rs
│       │   ├── publish.rs
│       │   └── resolve.rs
│       ├── external_ip/
│       │   ├── mod.rs
│       │   ├── providers/
│       │   │   ├── external_ip_resolver.rs
│       │   │   ├── icanhazip.rs
│       │   │   ├── identme.rs
│       │   │   ├── ipifyorg.rs
│       │   │   ├── ipinfoio.rs
│       │   │   ├── mod.rs
│       │   │   └── myip.rs
│       │   └── resolver.rs
│       ├── helpers.rs
│       ├── main.rs
│       ├── pkarr_packet.rs
│       └── simple_zone.rs
├── compose.yaml
├── docs/
│   ├── dns-over-https.md
│   ├── dyn-dns.md
│   └── logging.md
├── rustfmt.toml
├── server/
│   ├── Cargo.toml
│   ├── config.sample.toml
│   ├── pkdns.service
│   └── src/
│       ├── app_context.rs
│       ├── config/
│       │   ├── config_file.rs
│       │   ├── data_dir.rs
│       │   ├── mock_data_dir.rs
│       │   ├── mod.rs
│       │   ├── persistent_data_dir.rs
│       │   └── top_level_domain.rs
│       ├── dns_over_https/
│       │   ├── mod.rs
│       │   └── server.rs
│       ├── helpers.rs
│       ├── main.rs
│       └── resolution/
│           ├── dns_packets/
│           │   ├── mod.rs
│           │   ├── parsed_packet.rs
│           │   └── parsed_query.rs
│           ├── dns_socket.rs
│           ├── helpers.rs
│           ├── mod.rs
│           ├── pending_request.rs
│           ├── pkd/
│           │   ├── bootstrap_nodes.rs
│           │   ├── mod.rs
│           │   ├── pkarr_cache.rs
│           │   ├── pkarr_resolver.rs
│           │   ├── pubkey_parser.rs
│           │   └── query_matcher.rs
│           ├── query_id_manager.rs
│           ├── rate_limiter.rs
│           └── response_cache.rs
└── servers.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/actions/setup/action.yaml
================================================
name: Shared Setup

description: "Install Rust, checkout code and install dependencies."


runs:
  using: "composite"
  steps:
    - name: Checkout Code
      uses: actions/checkout@v3

    - name: Cache Cargo Dependencies
      uses: actions/cache@v3
      with:
        path: |
          ~/.cargo
          target
        key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
        restore-keys: |
          ${{ runner.os }}-cargo-

    - name: Install Rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: 1.87
        profile: minimal
        components: clippy, rustfmt
        override: true



================================================
FILE: .github/workflows/build-artifacts.yml
================================================
# This workflow builds the artifacts for the release.
# It creates the gzips and uploads them as artifacts.
# The artifacts can then be used to create a release on Github.

name: Build Artifacts

on:
  # This workflow is triggered every time a push is made to the main branch.
  # Aka when a PR is merged.
  push:
    branches: [master]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Setup the environment
      - name: Log in to GitHub Container Registry # Where we store the docker images with the cross compile toolchain for Apple
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: actions/checkout@v4
      - name: Set up Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
      - name: Install cross # Tool to cross-compile the binaries
        run: cargo install cross --git https://github.com/cross-rs/cross --rev 51f46f296253d8122c927c5bb933e3c4f27cc317


      # Build artifacts
      - name: Build Artifacts
        run: .scripts/build-artifacts.sh

      # Upload artifacts to Github
      - name: Gzip artifacts
        run: (cd target && tar -czf github-release.tar.gz github-release)
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: pkdns-artifacts.tar.gz
          path: target/github-release.tar.gz
          if-no-files-found: error
          overwrite: true



================================================
FILE: .github/workflows/rust.yaml
================================================
name: Pipeline

on:
  pull_request:
    branches:
      - "*"

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup environment
        id: setup
        uses: ./.github/actions/setup
      
      - name: Build Project
        run: cargo build --verbose

  test:
    name: Test
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v2
      - name: Setup environment
        id: setup
        uses: ./.github/actions/setup
      - name: Run Tests
        run: cargo test --verbose


  format:
    name: Check Format
    runs-on: ubuntu-latest
    needs: build

    steps:
      - uses: actions/checkout@v2
      - name: Setup environment
        id: setup
        uses: ./.github/actions/setup
      - name: Run Rustfmt (Code Formatting)
        run: cargo fmt --all -- --check


  clippy:
    name: Check Clippy
    runs-on: ubuntu-latest
    needs: build

    steps:
      - uses: actions/checkout@v2
      - name: Setup environment
        id: setup
        uses: ./.github/actions/setup

      - name: Run Clippy (Lint)
        run: cargo clippy --all-targets --all-features -- -D warnings

================================================
FILE: .gitignore
================================================
# 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
cli/7fmjpc

.vscode


================================================
FILE: .scripts/build-artifacts.sh
================================================
#!/bin/bash

# -------------------------------------------------------------------------------------------------
# This script prepares the artifacts for the current project.
# It builds all the binaries and prepares them for upload as a Github Release.
# The end result will be a target/github-release directory with the following structure:
#
# target/github-release/
# ├── pkdns-v0.5.0-rc.0-linux-arm64.tar.gz
# ├── pkdns-v0.5.0-rc.0-linux-amd64.tar.gz
# ├── pkdns-v0.5.0-rc.0-windows-amd64.tar.gz
# ├── pkdns-v0.5.0-rc.0-osx-arm64.tar.gz
# ├── pkdns-v0.5.0-rc.0-osx-amd64.tar.gz
# └── ...
#
# Make sure you installed https://github.com/cross-rs/cross for cross-compilation.
# To build macos, you need access to the [Pubky Github Packages](https://github.com/orgs/pubky/packages).
# Use [docker login](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic)
# to authenticate yourself.
# -------------------------------------------------------------------------------------------------


set -e # fail the script if any command fails
set -u # fail the script if any variable is not set
set -o pipefail # fail the script if any pipe command fails


# Check if cross is installed
if ! command -v cross &> /dev/null
then
    echo "cross executable could not be found. It is required to cross-compile the binaries. Please install it from https://github.com/cross-rs/cross"
    exit 1
fi

# Read the version from the homeserver
VERSION=$(cargo pkgid -p pkdns | cut -d@ -f2)
echo "Preparing release executables for version $VERSION..."
TARGETS=(
# target, nickname
"aarch64-apple-darwin,osx-arm64" 
"x86_64-apple-darwin,osx-amd64"
"aarch64-unknown-linux-musl,linux-arm64"
"x86_64-unknown-linux-musl,linux-amd64"
"x86_64-pc-windows-gnu,windows-amd64"
)

# List of binaries to build.
ARTIFACTS=("pkdns" "pkdns-cli")

echo "Create the github-release directory..."
rm -rf target/github-release
mkdir -p target/github-release

# Helper function to build an artifact for one specific target.
build_target() {
    local TARGET=$1
    local NICKNAME=$2
    echo "Build $NICKNAME with $TARGET"
    FOLDER="pkdns-v$VERSION-$NICKNAME"
    DICT="target/github-release/$FOLDER"
    mkdir -p $DICT
    for ARTIFACT in "${ARTIFACTS[@]}"; do
        echo "- Build $ARTIFACT with $TARGET"
        cross build -p $ARTIFACT --release --target $TARGET
        if [[ $TARGET == *"windows"* ]]; then
            cp target/$TARGET/release/$ARTIFACT.exe $DICT/
        else
            cp target/$TARGET/release/$ARTIFACT $DICT/
        fi
        echo "[Done] Artifact $ARTIFACT built for $TARGET"
    done;
    (cd target/github-release && tar -czf $FOLDER.tar.gz $FOLDER && rm -rf $FOLDER)
}

# Build the binaries
echo "Build all the binaries for version $VERSION..."
for ELEMENT in "${TARGETS[@]}"; do
    # Split tuple by comma
    IFS=',' read -r TARGET NICKNAME <<< "$ELEMENT"

    build_target $TARGET $NICKNAME
done

tree target/github-release
(cd target/github-release && pwd)

================================================
FILE: .scripts/build_docker.sh
================================================
#!/bin/bash

VERSION=$(cargo pkgid -p pkdns | cut -d@ -f2)
docker build --platform linux/amd64 -t synonymsoft/pkdns:$VERSION -t synonymsoft/pkdns:latest .


echo
read -p "Do you want to publish synonymsoft/pkdns:$VERSION to Docker Hub? [yN] " response
echo
case "$response" in
    [yY]|[yY][eE][sS])  # Accepts y, Y, yes, YES

        docker push synonymsoft/pkdns:$VERSION
        ;;
    *)
        echo "Dont publish synonymsoft/pkdns:$VERSION."
        exit 1
        ;;
esac

echo
read -p "Do you want to publish synonymsoft/pkdns:latest to Docker Hub? [yN] " response
echo
case "$response" in
    [yY]|[yY][eE][sS])  # Accepts y, Y, yes, YES

        docker push synonymsoft/pkdns:latest
        ;;
    *)
        echo "Dont publish synonymsoft/pkdns:latest."
        exit 1
        ;;
esac


================================================
FILE: Cargo.toml
================================================
[workspace]
members = [
  "server", 
  "cli"
]



# See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352
resolver = "2"


================================================
FILE: Cross.toml
================================================
# --------------------------------------------------------------------------------
# Costum defined Apple cross-compile images.
# They should not be public because of Apple licencing restrictions.
# https://github.com/cross-rs/cross/
# See https://github.com/cross-rs/cross-toolchains?tab=readme-ov-file#apple-targets
# SDKs can be downloaded here: https://github.com/joseluisq/macosx-sdks
# Images are uploaded to the Github Registry. Docker login required.
# https://github.com/orgs/pubky/packages
# @Sev May 2025
# --------------------------------------------------------------------------------


[target.aarch64-apple-darwin]
image = "ghcr.io/pubky/aarch64-apple-darwin-cross:latest"

[target.x86_64-apple-darwin]
image = "ghcr.io/pubky/x86_64-apple-darwin-cross:latest"

================================================
FILE: Dockerfile
================================================
# ========================
# Build Image
# ========================
FROM rust:1.86.0-alpine3.20 AS builder

# Install build dependencies
RUN apk add --no-cache \
    gcc \
    libc-dev

# Set the working directory
WORKDIR /usr/src/app

# Copy over Cargo.toml and Cargo.lock for dependency caching
COPY Cargo.toml Cargo.lock ./

# Copy over all the source code
COPY . .

# Build the project in release mode
RUN cargo build --release

# ========================
# Runtime Image
# ========================
FROM alpine:3.20

ARG TARGETARCH=x86_64

# Install runtime dependencies (only ca-certificates)
RUN apk add --no-cache ca-certificates

# Copy the compiled binary from the builder stage
COPY --from=builder /usr/src/app/target/release/pkdns /usr/local/bin
COPY --from=builder /usr/src/app/target/release/pkdns-cli /usr/local/bin

# Expose the DNS port
EXPOSE 53

# Set the default command to run the binary
CMD ["pkdns"]


================================================
FILE: LICENSE
================================================
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.

================================================
FILE: README.md
================================================
# pkdns

[![GitHub Release](https://img.shields.io/github/v/release/pubky/pkdns)](https://github.com/pubky/pkdns/releases/latest/)
[![Demo](https://img.shields.io/badge/Demo-7fmjpc-green)](http://pkdns-demo.pubky.app/)
[![Docker](https://img.shields.io/badge/Image-Docker-red)](https://hub.docker.com/r/synonymsoft/pkdns)
[![Telegram Chat Group](https://img.shields.io/badge/Chat-Telegram-violet)](https://t.me/pubkycore)

A DNS server providing self-sovereign and censorship-resistant domain names. It resolves records hosted on the [Mainline DHT](https://en.wikipedia.org/wiki/Mainline_DHT), the biggest DHT on the planet with ~10M nodes that services torrents since 15 years.


## Getting Started

### Hosted DNS

Use one of the [hosted DNS servers](./servers.txt) to try out pkdns quickly.

- [Verify](#verify-pkdns-is-working) the server is working.
- Configure your [browser](#use-dns-over-https-in-your-browser) or [system dns](#change-your-system-dns).
- [Browse](#browse-the-self-sovereign-web) the self-sovereign web.


### Pre-Built Binaries

1. Download the [latest release](https://github.com/pubky/pkdns/releases/latest/) for your plattform.
2. Extract the tar file. Should be something like `tar -xvf tarfile.tar.gz`.
3. Run `pkdns --verbose`.
4. [Verify](#verify-pkdns-is-working) the server is working. Your dns server ip is `127.0.0.1`.
5. Configure your [browser](#use-dns-over-https-in-your-browser) or [system dns](#change-your-system-dns).
6. [Browse](#browse-the-self-sovereign-web) the self-sovereign web.


### Build It Yourself

Make sure you have the [Rust toolchain](https://rustup.rs/) installed.

1. Clone repository `git clone https://github.com/pubky/pkdns.git`.
2. Switch directory `cd pkdns`.
3. Run `cargo run --package=pkdns`.
4. [Verify](#verify-pkdns-is-working) the server is working. Your server ip is `127.0.0.1`.
6. Configure your [browser](#use-dns-over-https-in-your-browser) or [system dns](#change-your-system-dns).
7. [Browse](#browse-the-self-sovereign-web) the self-sovereign web.


### Use Docker Compose

See [compose.yaml](./compose.yaml).

## Guides

### Use DNS-over-HTTPS in your Browser

1. Pick a DNS-over-HTTPS URL from our public [servers.txt](./servers.txt) list.
2. Configure your browser. See [this guide](https://support.privadovpn.com/kb/article/848-how-to-enable-doh-on-your-browser/).


Verify your server with this domain [http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/](http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/).

### Change your System DNS

Upgrade your whole machine to pkdns by setting it as your primary system DNS.

Follow one of the guides to change your DNS server on your system:
- [MacOS guide](https://support.apple.com/en-gb/guide/mac-help/mh14127)
- [Ubuntu guide](https://www.ionos.com/digitalguide/server/configuration/change-dns-server-on-ubuntu/)
- [Windows guide](https://www.windowscentral.com/how-change-your-pcs-dns-settings-windows-10)


Verify your server with this domain [http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/](http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/).

### Verify pkdns is working

#### PKDNS Domains
Verify the server resolves pkdns domains. Replace `PKDNS_SERVER_IP` with your pkdns server IP address.

```bash 
nslookup 7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy PKDNS_SERVER_IP
```

> *Troubleshooting* If this does not work then the pkdns server is likely not running or misconfigured.


#### ICANN Domains

Verify it resolves regular ICANN domains. Replace `PKDNS_SERVER_IP` with your pkdns server IP address.

```bash
nslookup example.com PKDNS_SERVER_IP
```

> *Troubleshooting* If this does not work then you need to change your ICANN fallback server with
> `pkdns -f REGULAR_DNS_SERVER_IP`. Or use the Google DNS server: `pkdns -f 8.8.8.8`.

### Browse the Self-Sovereign Web

Here are some example pkdns domains:

- [http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/](http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/)
- [http://pkdns.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/](http://pkdns.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/)


Hint: Always add a `./` to the end of a pkarr domain. Otherwise browsers will search instead of resolve the website.

### Address already in use

Other services might occupy the port 53 already. For example, [Docker Desktop](https://github.com/docker/for-mac/issues/7008) uses the port 53 on MacOS. [systemd-resolved](https://www.linuxuprising.com/2020/07/ubuntu-how-to-free-up-port-53-used-by.html) is using it on Ubuntu. Make sure to free those.

## Configuration

### Options

```
Usage: pkdns [OPTIONS]

Options:
  -f, --forward <FORWARD>      ICANN fallback DNS server. Format: IP:Port. [default: 8.8.8.8:53]
  -v, --verbose                Show verbose output. [default: false]
  -c, --config <CONFIG>        The path to pkdns configuration file. This will override the pkdns-dir config path
  -p, --pkdns-dir <PKDNS_DIR>  The base directory that contains pkdns's data, configuration file, etc [default: ~/.pkdns]
  -h, --help                   Print help
  -V, --version                Print version
```

### Config File

`~/.pkdns/pkdns.toml` is used for all extended configurations. An example can be found in [config.sample.toml](./server/config.sample.toml).


## FAQs

- [How Censorship-Resistant is Mainline DHT?](https://medium.com/pubky/mainline-dht-censorship-explained-b62763db39cb)
- [How Censorship-Resistant are Public Key Domains](https://medium.com/pubky/public-key-domains-censorship-resistance-explained-33d0333e6123)
- [How to publish a Public Key Domain Website?](https://medium.com/pubky/how-to-host-a-public-key-domain-website-v0-6-0-ubuntu-24-04-57e6f2cb6f77)
- [How can I run my own DNS over HTTPS endpoint?](./docs/dns-over-https.md)
- [How to configure DynDNS?](./docs/dyn-dns.md)

## Related Tools

- [pkarr zone explorer](https://pkdns.net/)
- [pkdns-vanity](https://github.com/jphastings/pkdns-vanity)
- [awesome-pubky](https://github.com/aljazceru/awesome-pubky)

### Record Types

Currently, pkdns only supports `A`, `AAAA`, `TXT`, `CNAME`, and `MX` records. For any other types, use bind9.


---

May the power ⚡ be with you. Powered by [pkarr](https://github.com/pubky/pkarr).


================================================
FILE: cli/Cargo.toml
================================================
[package]
name = "pkdns-cli"
version = "0.7.1"
authors = ["SeverinAlexB <severin@synonym.to>"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.82"
tokio = { version = "1.37.0", features = ["full"] }
thiserror = "1.0.49"
serde = { version = "1.0.199", features = ["derive"] }
clap = { version = "4.5.1", features = ["derive"] }
pkarr = { version = "3.8.0"}
domain = {version = "0.11.0", features = ["zonefile", "bytes"]}
bytes = "1.7.1"
chrono = "0.4.38"
shellexpand = "3.1.0"
zbase32 = "0.1.2"
ctrlc = "3.4.4"
reqwest = { version="0.12.12", default-features = false, features = ["json", "rustls-tls", "http2"]}
rand = {version = "0.8"}
hex = "0.4.3"

[dev-dependencies]
tempfile = "3.20.0"


================================================
FILE: cli/sample/README.md
================================================
## pkdns-cli sample

This is an example on how to announce your own records on the mainline DHT.

- `seed.txt` contains a 64 character hex encoded seed that the records are published under. You can generate one with `./pkdns-cli generate > seed.txt`.
- `pkarr.zone` is a dns zone file without the SOA record.

Publish the records by pointing to the seed and zone files.

> ⚠️ pkdns caches DHT packets for at least 60 seconds to improve latency. Run your own instance with `pkdns --max-ttl 0` to disable caching.

```bash
$ ./pkdns-cli publish seed.txt pkarr.zone

Packet 8qhdp5s8jjmxmqam3bpg9kzeg7x8teztuwrfgxw5ikn9z5bt15uy
Name                 TTL     Type   Data
@                    60      A      127.0.0.1                
dynv4                60      A      213.55.243.129           
dynv6                60      AAAA   2a04:ee41:0:819d:1905:9907:f695:41f3
text                 60      TXT    hero=satoshi2  

Announce every 60min. Stop with Ctrl-C...
2024-08-05 14:30:03.612747 +02:00 Successfully announced.
```

## Verify the Public Key Domain

- Lookup the domain on pkdns.net: https://pkdns.net/?id=8qhdp5s8jjmxmqam3bpg9kzeg7x8teztuwrfgxw5ikn9z5bt15uy
- Lookup the domain with nslookup: `nslookup 8qhdp5s8jjmxmqam3bpg9kzeg7x8teztuwrfgxw5ikn9z5bt15uy 34.65.109.99`. 34.65.109.99 is the IP of our hosted pkdns server.

================================================
FILE: cli/sample/pkarr.zone
================================================
$TTL 60

@      IN    A      127.0.0.1
dynv4  IN    A      {external_ipv4}
dynv6  IN    AAAA   {external_ipv6}

text   IN    TXT    hero=satoshi2


================================================
FILE: cli/sample/seed.txt
================================================
292bbfb6a5bc1ec3f6d01a7c8e3fac1782d364ca41a1847497e9e3bc949dbb9a


================================================
FILE: cli/src/cli.rs
================================================
use crate::commands::{cli_publickey, generate::cli_generate_seed, publish::cli_publish, resolve::cli_resolve};

/**
 * Main cli entry function.
 */
pub async fn run_cli() {
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    let cmd = clap::Command::new("pkdns-cli")
        .version(VERSION)
        .arg_required_else_help(true)
        .subcommand(
            clap::Command::new("publish")
                .about("Publish pkarr dns records.")
                .arg(
                    clap::Arg::new("seed")
                        .help("File path to the seed file.")
                        .default_value("./seed.txt"),
                )
                .arg(
                    clap::Arg::new("zonefile")
                        .help("File path to the dns zone file.")
                        .default_value("./pkarr.zone"),
                ),
        )
        .subcommand(
            clap::Command::new("resolve")
                .about("Resolve a public key domain on the DHT.")
                .arg_required_else_help(true)
                .arg(clap::Arg::new("pubkey").required(false).help("Public Key Domain")),
        )
        .subcommand(clap::Command::new("generate").about("Generate a new seed"))
        .subcommand(
            clap::Command::new("publickey")
                .about("Derive the public key from the seed.")
                .arg(
                    clap::Arg::new("seed")
                        .required(false)
                        .help("File path to the pkarr seed file.")
                        .default_value("./seed.txt"),
                ),
        );
    let matches = cmd.get_matches();

    match matches.subcommand() {
        Some(("resolve", matches)) => {
            cli_resolve(matches).await;
        }
        Some(("publish", matches)) => {
            cli_publish(matches).await;
        }
        Some(("generate", matches)) => {
            cli_generate_seed(matches).await;
        }
        Some(("publickey", matches)) => {
            cli_publickey(matches).await;
        }
        _ => {
            unimplemented!("command not implemented")
        }
    };
}


================================================
FILE: cli/src/commands/generate.rs
================================================
use clap::ArgMatches;
use pkarr::Keypair;

pub async fn cli_generate_seed(_matches: &ArgMatches) {
    let keypair = Keypair::random();
    let encoded = hex::encode(keypair.secret_key());
    println!("{encoded}");
}


================================================
FILE: cli/src/commands/mod.rs
================================================
pub mod generate;
mod publickey;
pub mod publish;
pub mod resolve;

pub use publickey::cli_publickey;


================================================
FILE: cli/src/commands/publickey.rs
================================================
use clap::ArgMatches;
use pkarr::Keypair;
use std::{
    fs::read_to_string,
    path::{Path, PathBuf},
};

const SECRET_KEY_LENGTH: usize = 32;

fn read_seed_file(matches: &ArgMatches) -> Keypair {
    let unexpanded_path: &String = matches.get_one("seed").unwrap();
    let expanded_path: String = shellexpand::full(unexpanded_path).expect("Valid shell path.").into();
    let path = Path::new(&expanded_path);
    let path = PathBuf::from(path);

    let seed = read_to_string(path);
    if let Err(e) = seed {
        eprintln!("Failed to read seed at {expanded_path}. {e}");
        std::process::exit(1);
    };
    let seed = seed.unwrap();
    parse_seed(&seed)
}

fn parse_seed(seed: &str) -> Keypair {
    let seed = seed.trim();
    let decode_result = zbase32::decode_full_bytes_str(seed);
    if let Err(e) = decode_result {
        eprintln!("Failed to parse the seed file. {e} {seed}");
        std::process::exit(1);
    };

    let plain_secret = decode_result.unwrap();

    let slice: &[u8; SECRET_KEY_LENGTH] = &plain_secret[0..SECRET_KEY_LENGTH].try_into().unwrap();

    Keypair::from_secret_key(slice)
}

pub async fn cli_publickey(matches: &ArgMatches) {
    let keypair = read_seed_file(matches);
    let pubkey = keypair.to_z32();

    println!("{pubkey}");
}


================================================
FILE: cli/src/commands/publish.rs
================================================
use std::io::Write;
use std::{
    fs::read_to_string,
    path::{Path, PathBuf},
};

use anyhow::anyhow;

use clap::ArgMatches;
use pkarr::{Keypair, SignedPacket, Timestamp};

use crate::external_ip::{resolve_ipv4, resolve_ipv6};
use crate::helpers::nts_to_chrono;
use crate::{helpers::construct_pkarr_client, simple_zone::SimpleZone};

const SECRET_KEY_LENGTH: usize = 32;

/// Replaces {externl_ipv4} and {external_ipv6} variables in the zone file
/// with the according ips.
/// Errors if ips can't be resolved.
async fn fill_dyndns_variables(zone: &mut String) -> Result<(), anyhow::Error> {
    if zone.contains("{external_ipv4}") {
        let ip_result = resolve_ipv4().await;
        if let Err(e) = ip_result {
            return Err(anyhow!(e));
        }
        let (external_ipv4, _) = ip_result.unwrap();
        *zone = zone.replace("{external_ipv4}", &external_ipv4.to_string());
    };

    if zone.contains("{external_ipv6}") {
        let ip_result = resolve_ipv6().await;
        if let Err(e) = ip_result {
            return Err(anyhow!(e));
        }
        let (external_ipv6, _) = ip_result.unwrap();
        *zone = zone.replace("{external_ipv6}", &external_ipv6.to_string());
    };

    Ok(())
}

async fn read_zone_file(zone_file_path: &str, pubkey: &str) -> SimpleZone {
    let csv_path_str: String = shellexpand::full(zone_file_path).expect("Valid shell path.").into();
    let path = Path::new(&csv_path_str);
    let path = PathBuf::from(path);

    let zone = read_to_string(path);
    if let Err(e) = zone {
        eprintln!("Failed to read zone at {csv_path_str}. {e}");
        std::process::exit(1);
    };
    let mut zone = zone.unwrap();
    if let Err(e) = fill_dyndns_variables(&mut zone).await {
        panic!("Failed to fetch external ips. {e}");
    };

    let zone = SimpleZone::read(zone, pubkey);
    if let Err(e) = zone {
        eprintln!("Failed to parse zone file. {e}");
        std::process::exit(1);
    };
    zone.unwrap()
}

fn read_seed_file(seed_file_path: &str) -> Keypair {
    let expanded_path: String = shellexpand::full(seed_file_path).expect("Valid shell path.").into();
    let path = Path::new(&expanded_path);
    let path = PathBuf::from(path);

    let seed = std::fs::read_to_string(path);
    if let Err(e) = seed {
        eprintln!("Failed to read seed at {expanded_path}. {e}");
        std::process::exit(1);
    };
    let seed = seed.unwrap();
    match parse_seed(&seed) {
        Ok(keypair) => keypair,
        Err(e) => {
            eprintln!("Failed to parse the seed file. {e} {seed}");
            std::process::exit(1);
        }
    }
}

/// Parse a seed file into a keypair.
/// Tries to parse as hex first, then as zbase32.
/// Errors if the seed is not valid.
fn parse_seed(seed: &str) -> anyhow::Result<Keypair> {
    let seed = seed.trim();

    if seed.len() == 52 {
        return parse_seed_zbase32(seed);
    }

    parse_seed_hex(seed)
}

/// Parse a hex seed into a keypair.
/// The seed is expected to be 64 characters long.
/// This is the new format of the seed.
fn parse_seed_hex(seed: &str) -> anyhow::Result<Keypair> {
    let decode_result = hex::decode(seed)?;
    let slice: &[u8; SECRET_KEY_LENGTH] = &decode_result[0..SECRET_KEY_LENGTH].try_into()?;
    Ok(Keypair::from_secret_key(slice))
}

/// Parse a zbase32 seed into a keypair.
/// The seed is expected to be 52 characters long.
/// This is the old format of the seed.
fn parse_seed_zbase32(seed: &str) -> anyhow::Result<Keypair> {
    let decode_result = match zbase32::decode_full_bytes_str(seed) {
        Ok(bytes) => bytes,
        Err(_) => return Err(anyhow!("Invalid zbase32 seed")),
    };
    let slice: &[u8; SECRET_KEY_LENGTH] = &decode_result[0..SECRET_KEY_LENGTH].try_into()?;
    Ok(Keypair::from_secret_key(slice))
}

pub async fn cli_publish(matches: &ArgMatches) {
    let seed_file_path: &String = matches.get_one("seed").expect("--seed file path is required");
    let zone_file_path: &String = matches.get_one("zonefile").expect("--zonefile file path is required");

    let keypair = read_seed_file(seed_file_path.as_str());
    let pubkey = keypair.to_z32();
    let client = construct_pkarr_client();

    let zone = read_zone_file(zone_file_path.as_str(), &pubkey).await;
    println!("{}", zone.packet);
    let packet = zone.packet.parsed();
    let packet = SignedPacket::new(&keypair, &packet.answers, Timestamp::now());
    if let Err(e) = packet {
        eprintln!("Failed to sign the pkarr packet. {e}");
        std::process::exit(1);
    }
    let packet = packet.unwrap();

    print!("Hang on... {}", nts_to_chrono(packet.timestamp()));
    std::io::stdout().flush().unwrap();
    let result = client.publish(&packet, None).await;
    print!("\r");
    match result {
        Ok(_) => {
            println!("{} Successfully announced.", nts_to_chrono(packet.timestamp()))
        }
        Err(e) => {
            println!("Error {}", e)
        }
    };
}


================================================
FILE: cli/src/commands/resolve.rs
================================================
use chrono::{DateTime, Utc};
use clap::ArgMatches;
use pkarr::PublicKey;

use crate::{
    helpers::{construct_pkarr_client, nts_to_chrono},
    pkarr_packet::PkarrPacket,
};

async fn resolve_pkarr(uri: &str) -> (PkarrPacket, DateTime<Utc>) {
    let client = construct_pkarr_client();
    let pubkey: PublicKey = uri.try_into().expect("Should be valid pkarr public key.");
    let res = client.resolve_most_recent(&pubkey).await;
    if res.is_none() {
        println!("Failed to find the packet.");
        return (PkarrPacket::empty(), DateTime::<Utc>::MIN_UTC);
    };
    let signed_packet = res.unwrap();
    let timestamp = nts_to_chrono(signed_packet.timestamp());

    let data = signed_packet.encoded_packet();

    (PkarrPacket::by_data(data.to_vec()), timestamp)
}

fn get_arg_pubkey(matches: &ArgMatches) -> Option<PublicKey> {
    let uri_arg: &String = matches.get_one("pubkey").unwrap();
    let trying: Result<PublicKey, _> = uri_arg.as_str().try_into();
    trying.ok()
}

pub async fn cli_resolve(matches: &ArgMatches) {
    let pubkey_opt = get_arg_pubkey(matches);

    if pubkey_opt.is_none() {
        eprintln!("pubkey is not a valid pkarr public key.");
        std::process::exit(1);
    };
    let pubkey = pubkey_opt.unwrap();
    let uri = pubkey.to_uri_string();

    println!("Resolve dns records of {}", uri);
    let (packet, timestamp) = resolve_pkarr(&uri).await;

    println!("{packet}");
    if !packet.is_emtpy() {
        println!("Last updated at: {timestamp}");
    };
}


================================================
FILE: cli/src/external_ip/mod.rs
================================================
mod providers;
mod resolver;

pub use resolver::{resolve_ipv4, resolve_ipv6};


================================================
FILE: cli/src/external_ip/providers/external_ip_resolver.rs
================================================
use std::{
    future::Future,
    net::{AddrParseError, Ipv4Addr, Ipv6Addr},
    pin::Pin,
};

use reqwest::IntoUrl;

type ExternalIpResult<T> = Result<T, ExternalIpResolverError>;

type IpFuture<T> = Pin<Box<dyn Future<Output = ExternalIpResult<T>>>>;

type IpResolver<T> = Pin<Box<dyn Fn() -> IpFuture<T>>>;

pub struct ProviderResolver {
    pub name: String,
    ipv4: IpResolver<Ipv4Addr>,
    ipv6: IpResolver<Ipv6Addr>,
}

impl ProviderResolver {
    pub fn new(name: String, ipv4: IpResolver<Ipv4Addr>, ipv6: IpResolver<Ipv6Addr>) -> Self {
        Self { name, ipv4, ipv6 }
    }

    /// Resolve this computers external ipv4 address.
    pub async fn ipv4(&self) -> Result<Ipv4Addr, ExternalIpResolverError> {
        let func = &self.ipv4;
        func().await
    }

    /// Resolve this computers external ipv6 address.
    pub async fn ipv6(&self) -> Result<Ipv6Addr, ExternalIpResolverError> {
        let func = &self.ipv6;
        func().await
    }
}

#[derive(thiserror::Error, Debug)]
pub enum ExternalIpResolverError {
    #[error(transparent)]
    IO(#[from] reqwest::Error),

    #[error(transparent)]
    IpParse(#[from] AddrParseError),
}

/// Resolves a url return the Ipv4 in it's response.
pub async fn resolve_ipv4_with_url<T: IntoUrl>(url: T) -> Result<Ipv4Addr, ExternalIpResolverError> {
    let response = reqwest::get(url).await?;
    let text = response.text().await?;
    let text = text.trim();
    let ip: Ipv4Addr = text.parse()?;
    Ok(ip)
}

/// Resolves a url return the Ipv6 in it's response.
pub async fn resolve_ipv6_with_url<T: IntoUrl>(url: T) -> Result<Ipv6Addr, ExternalIpResolverError> {
    let response = reqwest::get(url).await?;
    let text = response.text().await?;
    let text = text.trim();
    let ip: Ipv6Addr = text.parse()?;
    Ok(ip)
}


================================================
FILE: cli/src/external_ip/providers/icanhazip.rs
================================================
use std::net::{Ipv4Addr, Ipv6Addr};

use super::external_ip_resolver::{
    resolve_ipv4_with_url, resolve_ipv6_with_url, ExternalIpResolverError, ProviderResolver,
};

pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
    resolve_ipv4_with_url("https://ipv4.icanhazip.com").await
}

pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
    resolve_ipv6_with_url("https://ipv6.icanhazip.com").await
}

pub fn get_resolver() -> ProviderResolver {
    ProviderResolver::new(
        "icanhazip.com".to_string(),
        Box::pin(move || Box::pin(resolve_ipv4())),
        Box::pin(move || Box::pin(resolve_ipv6())),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_ipv4() {
        let ip = resolve_ipv4().await;
        assert!(ip.is_ok());
    }

    #[ignore = "Github runners don't support ipv6 request."]
    #[tokio::test]
    async fn test_ipv6() {
        let ip = resolve_ipv6().await;
        assert!(ip.is_ok());
    }
}


================================================
FILE: cli/src/external_ip/providers/identme.rs
================================================
use std::net::{Ipv4Addr, Ipv6Addr};

use super::external_ip_resolver::{
    resolve_ipv4_with_url, resolve_ipv6_with_url, ExternalIpResolverError, ProviderResolver,
};

pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
    resolve_ipv4_with_url("https://v4.ident.me").await
}

pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
    resolve_ipv6_with_url("https://v6.ident.me").await
}

pub fn get_resolver() -> ProviderResolver {
    ProviderResolver::new(
        "ident.me".to_string(),
        Box::pin(move || Box::pin(resolve_ipv4())),
        Box::pin(move || Box::pin(resolve_ipv6())),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_ipv4() {
        let ip = resolve_ipv4().await;
        assert!(ip.is_ok());
    }

    #[ignore = "Github runners don't support ipv6 request."]
    #[tokio::test]
    async fn test_ipv6() {
        let ip = resolve_ipv6().await;
        assert!(ip.is_ok());
    }
}


================================================
FILE: cli/src/external_ip/providers/ipifyorg.rs
================================================
use std::net::{Ipv4Addr, Ipv6Addr};

use super::external_ip_resolver::{
    resolve_ipv4_with_url, resolve_ipv6_with_url, ExternalIpResolverError, ProviderResolver,
};

pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
    resolve_ipv4_with_url("https://api.ipify.org").await
}

pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
    resolve_ipv6_with_url("https://api6.ipify.org").await
}

pub fn get_resolver() -> ProviderResolver {
    ProviderResolver::new(
        "ipify.org".to_string(),
        Box::pin(move || Box::pin(resolve_ipv4())),
        Box::pin(move || Box::pin(resolve_ipv6())),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_ipv4() {
        let ip = resolve_ipv4().await;
        assert!(ip.is_ok());
    }

    #[ignore = "Github runners don't support ipv6 request."]
    #[tokio::test]
    async fn test_ipv6() {
        let ip = resolve_ipv6().await;
        assert!(ip.is_ok());
    }
}


================================================
FILE: cli/src/external_ip/providers/ipinfoio.rs
================================================
use std::net::{Ipv4Addr, Ipv6Addr};

use serde::Deserialize;

use super::external_ip_resolver::{ExternalIpResolverError, ProviderResolver};

#[derive(Debug, Deserialize)]
struct IpResponse {
    pub ip: String,
}

pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
    let response = reqwest::get("https://ipinfo.io").await?;
    let text = response.json::<IpResponse>().await?;
    let ip: Ipv4Addr = text.ip.parse()?;
    Ok(ip)
}

pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
    let response = reqwest::get("https://v6.ipinfo.io").await?;
    let text = response.json::<IpResponse>().await?;
    let ip: Ipv6Addr = text.ip.parse()?;
    Ok(ip)
}

pub fn get_resolver() -> ProviderResolver {
    ProviderResolver::new(
        "ipinfo.io".to_string(),
        Box::pin(move || Box::pin(resolve_ipv4())),
        Box::pin(move || Box::pin(resolve_ipv6())),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_ipv4() {
        let ip = resolve_ipv4().await;
        assert!(ip.is_ok());
    }

    #[ignore = "Github runners don't support ipv6 request."]
    #[tokio::test]
    async fn test_ipv6() {
        let ip = resolve_ipv6().await;
        assert!(ip.is_ok());
    }
}


================================================
FILE: cli/src/external_ip/providers/mod.rs
================================================
pub mod icanhazip;
pub mod identme;
pub mod ipifyorg;
pub mod ipinfoio;
pub mod myip;

mod external_ip_resolver;
pub use external_ip_resolver::ProviderResolver;


================================================
FILE: cli/src/external_ip/providers/myip.rs
================================================
use std::net::{Ipv4Addr, Ipv6Addr};

use serde::Deserialize;

use super::external_ip_resolver::{ExternalIpResolverError, ProviderResolver};

#[derive(Debug, Deserialize)]
struct IpResponse {
    pub ip: String,
}

pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
    let response = reqwest::get("https://4.myip.is/").await?;
    let text = response.json::<IpResponse>().await?;
    let ip: Ipv4Addr = text.ip.parse()?;
    Ok(ip)
}

pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
    let response = reqwest::get("https://6.myip.is/").await?;
    let text = response.json::<IpResponse>().await?;
    let ip: Ipv6Addr = text.ip.parse()?;
    Ok(ip)
}

pub fn get_resolver() -> ProviderResolver {
    ProviderResolver::new(
        "myip.is".to_string(),
        Box::pin(move || Box::pin(resolve_ipv4())),
        Box::pin(move || Box::pin(resolve_ipv6())),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_ipv4() {
        let ip = resolve_ipv4().await;
        assert!(ip.is_ok());
    }

    #[ignore = "Github runners don't support ipv6 request."]
    #[tokio::test]
    async fn test_ipv6() {
        let ip = resolve_ipv6().await;
        assert!(ip.is_ok());
    }
}


================================================
FILE: cli/src/external_ip/resolver.rs
================================================
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::net::{Ipv4Addr, Ipv6Addr};

use super::providers::ProviderResolver;
use super::providers::{icanhazip, identme, ipifyorg, ipinfoio, myip};

/// Resolves the external IPv4 address randomly from a list of 5 service providers.
/// Returns IP and the name of the service provider.
pub async fn resolve_ipv4() -> Result<(Ipv4Addr, String), &'static str> {
    let mut providers: Vec<ProviderResolver> = vec![
        icanhazip::get_resolver(),
        identme::get_resolver(),
        ipifyorg::get_resolver(),
        ipinfoio::get_resolver(),
        myip::get_resolver(),
    ];

    let mut rng = thread_rng();
    providers.shuffle(&mut rng);

    for provider in providers {
        match provider.ipv4().await {
            Ok(ip) => return Ok((ip, provider.name.clone())),
            Err(e) => {
                println!("Failed to fetch ip from {}. {e}", provider.name);
            }
        }
    }

    Err("All ip providers failed to return the external ip.")
}

/// Resolves the external IPv6 address randomly from a list of 5 service providers.
/// Returns IP and the name of the service provider.
pub async fn resolve_ipv6() -> Result<(Ipv6Addr, String), &'static str> {
    let mut providers: Vec<ProviderResolver> = vec![
        icanhazip::get_resolver(),
        identme::get_resolver(),
        ipifyorg::get_resolver(),
        ipinfoio::get_resolver(),
        myip::get_resolver(),
    ];

    let mut rng = thread_rng();
    providers.shuffle(&mut rng);

    for provider in providers {
        match provider.ipv6().await {
            Ok(ip) => return Ok((ip, provider.name.clone())),
            Err(e) => {
                println!("Failed to fetch ip from {}. {e}", provider.name);
            }
        }
    }

    Err("All ip providers failed to return the external ip.")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_ipv4() {
        let ip = resolve_ipv4().await;
        println!("{:?}", ip.expect("Valid ipv4"));
    }

    #[ignore = "Github runners don't support ipv6 request."]
    #[tokio::test]
    async fn test_ipv6() {
        let ip = resolve_ipv6().await;
        println!("{:?}", ip.expect("Valid ipv6"));
    }
}


================================================
FILE: cli/src/helpers.rs
================================================
use pkarr::{Client, Timestamp};

/// Construct new pkarr client with no cache, no resolver, only DHT
pub fn construct_pkarr_client() -> Client {
    let client = Client::builder().maximum_ttl(0).build().unwrap();
    client
}

// Turns a pkarr ntimestamp into a chrono timestamp
pub fn nts_to_chrono(ntc: Timestamp) -> chrono::DateTime<chrono::Utc> {
    chrono::DateTime::from_timestamp((ntc.as_u64() / 1000000).try_into().unwrap(), 0).unwrap()
}


================================================
FILE: cli/src/main.rs
================================================
use cli::run_cli;

mod cli;

mod commands;
mod external_ip;
mod helpers;
mod pkarr_packet;
mod simple_zone;

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    run_cli().await;
    Ok(())
}


================================================
FILE: cli/src/pkarr_packet.rs
================================================
use core::fmt;
use std::net::{Ipv4Addr, Ipv6Addr};

use anyhow::anyhow;
use pkarr::dns::{rdata::RData, Name, Packet, ResourceRecord};

/**
 * Full pkarr dns packet. All data is saved in the answers.
 */
#[derive(Debug)]
pub struct PkarrPacket {
    pub data: Vec<u8>,
}

impl PkarrPacket {
    pub fn empty() -> Self {
        let packet = Packet::new_reply(0);
        let data = packet.build_bytes_vec_compressed().unwrap();
        Self { data }
    }

    pub fn by_data(data: Vec<u8>) -> Self {
        Self { data }
    }

    pub fn parsed(&self) -> Packet {
        Packet::parse(&self.data).unwrap()
    }

    pub fn to_records(&self) -> Vec<PkarrRecord> {
        self.parsed()
            .answers
            .iter()
            .map(|answer| PkarrRecord::by_resource_record(answer))
            .collect()
    }

    pub fn answers_len(&self) -> usize {
        self.parsed().answers.len()
    }

    pub fn is_emtpy(&self) -> bool {
        self.answers_len() == 0
    }
}

impl fmt::Display for PkarrPacket {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.answers_len() == 0 {
            writeln!(f, "Packet is empty.")?;
            return Ok(());
        };
        let records = self.to_records();
        writeln!(f, "Packet {}", records.first().unwrap().pubkey())?;
        writeln!(f, "Name TTL Type Data")?;
        for record in self.to_records() {
            writeln!(f, "{record}")?;
        }
        Ok(())
    }
}

/**
 * Simple Pkarr record
 */
pub struct PkarrRecord {
    pub data: Vec<u8>,
}

impl PkarrRecord {
    #[allow(dead_code)]
    pub fn by_data(data: Vec<u8>) -> Result<Self, anyhow::Error> {
        let result = Packet::parse(&data);
        if let Err(e) = result {
            return Err(e.into());
        }
        let packet = result.unwrap();
        if packet.answers.len() != 1 {
            return Err(anyhow!("packet data must contain 1 answer."));
        }

        Ok(Self { data })
    }

    pub fn by_resource_record(rr: &ResourceRecord) -> Self {
        let rr = rr.clone();
        let mut packet = Packet::new_reply(0);
        packet.answers.push(rr);
        Self {
            data: packet.build_bytes_vec_compressed().unwrap(),
        }
    }

    pub fn get_resource_record(&self) -> ResourceRecord {
        let packet = Packet::parse(&self.data).unwrap();
        packet.answers[0].clone()
    }

    pub fn pubkey(&self) -> String {
        let rr = self.get_resource_record();
        let pubkey = rr.name.get_labels().last().unwrap();
        pubkey.to_string()
    }

    pub fn name(&self) -> String {
        let pubkey = self.pubkey();

        let name = Name::try_from(pubkey.as_str()).unwrap();
        let rr = self.get_resource_record();
        let name = rr.name.without(&name);
        match name {
            Some(n) => n.to_string(),
            None => "@".to_string(),
        }
    }

    pub fn ttl(&self) -> u32 {
        let rr = self.get_resource_record();
        rr.ttl
    }

    pub fn data_as_strings(&self) -> (&str, String) {
        let (record_type, data) = match self.get_resource_record().rdata {
            RData::A(a) => {
                let ipv4 = Ipv4Addr::from(a.address);
                ("A", ipv4.to_string())
            }
            RData::AAAA(val) => {
                let ipv6 = Ipv6Addr::from(val.address);
                ("AAAA", ipv6.to_string())
            }
            RData::CNAME(val) => {
                let data = val.to_string();
                ("CNAME", data)
            }
            RData::MX(val) => {
                let data = format!("{} - {}", val.preference, val.exchange);
                ("MX", data)
            }
            RData::TXT(val) => {
                let data = val
                    .attributes()
                    .iter()
                    .map(|(key, val)| {
                        if val.is_some() {
                            format!("{}={}", key, val.clone().unwrap())
                        } else {
                            format!("{}=", key)
                        }
                    })
                    .collect::<Vec<String>>()
                    .join(", ");
                ("TXT", data)
            }
            RData::NS(val) => {
                let data = val.to_string();
                ("NS", data)
            }
            _ => ("Unknown", "Unknown".to_string()),
        };
        (record_type, data)
    }
}

impl fmt::Display for PkarrRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let name = self.name();
        let ttl = self.ttl();
        let data = self.data_as_strings();
        write!(f, "{0: <20} {1: <7} {2: <6} {3: <25}", name, ttl, data.0, data.1)
    }
}


================================================
FILE: cli/src/simple_zone.rs
================================================
use anyhow::anyhow;
use domain::{
    base::Rtype,
    zonefile::inplace::{Entry, Zonefile},
};
use pkarr::dns::{Name, Packet, ResourceRecord};
use std::io::Cursor;

use crate::pkarr_packet::PkarrPacket;

/**
 * Reads a dns zone file without the otherwise necessary SOA entry.
 */
#[derive(Debug)]
pub struct SimpleZone {
    pub packet: PkarrPacket,
}

impl SimpleZone {
    /**
     * Read the zone data. Pkarr pubkey must be provided in zbase32 format.
     */
    pub fn read(simplified_zone: String, pubkey: &str) -> Result<Self, anyhow::Error> {
        let entries = Self::parse_simplified_zone(simplified_zone, pubkey)?;
        let packet = Self::entries_to_simple_dns_packet(entries)?;
        Ok(Self {
            packet: PkarrPacket { data: packet },
        })
    }

    /**
     * Generate a fake soa entry to simplify the
     * zone file the user needs to write.
     */
    fn generate_soa(pubkey: &str) -> String {
        let formatted = format!(
            "$ORIGIN {pubkey}. 
$TTL 300
@	IN	SOA	127.0.0.1.	hostmaster.example.com. (
			2001062501 ; serial                     
			21600      ; refresh after 6 hours                     
			3600       ; retry after 1 hour                     
			604800     ; expire after 1 week                     
			300 )    ; minimum TTL of 1 day  
            "
        );
        formatted
    }

    /**
     * Parses the zone data. Returns domain::Entry list.
     */
    fn parse_simplified_zone(simplified_zone: String, pubkey: &str) -> Result<Vec<Entry>, anyhow::Error> {
        let raw_soa = SimpleZone::generate_soa(pubkey);
        let zone = format!("{raw_soa}\n{simplified_zone}\n");

        let byte_data = zone.into_bytes();
        let mut cursor = Cursor::new(byte_data);
        let zone = Zonefile::load(&mut cursor)?;

        let mut entries: Vec<Entry> = vec![];
        for entry_res in zone.into_iter() {
            let entry = entry_res?;

            let should_include: bool = match entry.clone() {
                Entry::Record(val) => val.rtype() != Rtype::SOA,
                _ => false,
            };
            if should_include {
                entries.push(entry);
            }
        }
        Ok(entries)
    }

    /**
     * Converts domain::Entry to simple-dns packet bytes.
     */
    fn entries_to_simple_dns_packet(entries: Vec<Entry>) -> Result<Vec<u8>, anyhow::Error> {
        let mut packets = vec![];
        for entry in entries.iter() {
            let entry = entry.clone();
            let packet = match entry {
                Entry::Include { .. } => continue,
                Entry::Record(val) => {
                    let ttl = val.ttl().as_secs();
                    let (name, data) = val.clone().into_owner_and_data();
                    let simple_name_str = name.to_string();
                    let simple_name = Name::try_from(simple_name_str.as_str())?;
                    let simple_data = match data {
                        domain::rdata::ZoneRecordData::A(val) => {
                            let rdata: pkarr::dns::rdata::RData = pkarr::dns::rdata::RData::A(pkarr::dns::rdata::A {
                                address: val.addr().into(),
                            });
                            let rr = ResourceRecord::new(simple_name, pkarr::dns::CLASS::IN, ttl, rdata);
                            let mut packet = pkarr::dns::Packet::new_reply(0);
                            packet.answers.push(rr);
                            packet.build_bytes_vec_compressed()?
                        }
                        domain::rdata::ZoneRecordData::Aaaa(val) => {
                            let rdata: pkarr::dns::rdata::RData =
                                pkarr::dns::rdata::RData::AAAA(pkarr::dns::rdata::AAAA {
                                    address: val.addr().into(),
                                });
                            let rr = ResourceRecord::new(simple_name, pkarr::dns::CLASS::IN, ttl, rdata);
                            let mut packet = pkarr::dns::Packet::new_reply(0);
                            packet.answers.push(rr);
                            packet.build_bytes_vec_compressed()?
                        }
                        domain::rdata::ZoneRecordData::Ns(val) => {
                            let ns_name = val.to_string();
                            let rdata: pkarr::dns::rdata::RData =
                                pkarr::dns::rdata::RData::NS(pkarr::dns::rdata::NS(Name::try_from(ns_name.as_str())?));

                            let rr = ResourceRecord::new(simple_name, pkarr::dns::CLASS::IN, ttl, rdata);
                            let mut packet = pkarr::dns::Packet::new_reply(0);
                            packet.answers.push(rr);
                            packet.build_bytes_vec_compressed()?
                        }

                        domain::rdata::ZoneRecordData::Txt(val) => {
                            let mut txt = pkarr::dns::rdata::TXT::new();

                            for bytes in val.iter() {
                                let ascii = std::str::from_utf8(bytes).unwrap();
                                txt.add_string(ascii)?;
                            }
                            let rdata: pkarr::dns::rdata::RData = pkarr::dns::rdata::RData::TXT(txt);

                            let rr = ResourceRecord::new(simple_name, pkarr::dns::CLASS::IN, ttl, rdata);
                            let mut packet = pkarr::dns::Packet::new_reply(0);
                            packet.answers.push(rr);
                            packet.build_bytes_vec_compressed()?
                        }
                        domain::rdata::ZoneRecordData::Mx(val) => {
                            let exchange = val.exchange().to_string();
                            let mx = pkarr::dns::rdata::MX {
                                preference: val.preference(),
                                exchange: Name::try_from(exchange.as_str())?,
                            };

                            let rdata: pkarr::dns::rdata::RData = pkarr::dns::rdata::RData::MX(mx);

                            let rr = ResourceRecord::new(simple_name, pkarr::dns::CLASS::IN, ttl, rdata);
                            let mut packet = pkarr::dns::Packet::new_reply(0);
                            packet.answers.push(rr);
                            packet.build_bytes_vec_compressed()?
                        }
                        domain::rdata::ZoneRecordData::Cname(val) => {
                            let value = val.to_string();
                            let value = Name::try_from(value.as_str()).unwrap();
                            let rdata: pkarr::dns::rdata::RData =
                                pkarr::dns::rdata::RData::CNAME(pkarr::dns::rdata::CNAME(value));
                            let rr = ResourceRecord::new(simple_name, pkarr::dns::CLASS::IN, ttl, rdata);
                            let mut packet = pkarr::dns::Packet::new_reply(0);
                            packet.answers.push(rr);
                            packet.build_bytes_vec_compressed()?
                        }
                        _ => return Err(anyhow!("Not support record type.")),
                    };
                    simple_data
                }
            };
            packets.push(packet);
        }
        let mut final_packet = Packet::new_reply(0);
        for packet in packets.iter() {
            let parsed = Packet::parse(packet)?;
            for answer in parsed.answers {
                final_packet.answers.push(answer)
            }
        }
        Ok(final_packet.build_bytes_vec_compressed()?)
    }
}

#[cfg(test)]
mod tests {

    // Note this useful idiom: importing names from outer (for mod tests) scope.
    use super::*;

    fn simplified_zone() -> String {
        String::from(
            "
@	IN	NS	dns1.example.com. 
@ 400	IN	NS	dns2.example.com.        
	
	
@ 301	IN	MX	10	mail.example.com.       
@	IN	MX	20	mail2.example.com.   

@   IN  A 127.0.0.1
test   IN  A 127.0.0.1

	
dns1	IN	A	10.0.1.1
dns2	IN	A	10.0.1.2

text    IN  TXT  hero=satoshi 
",
        )
    }

    #[test]
    fn test_create_entries() {
        let simplified_zone = simplified_zone();
        let zone = SimpleZone::read(simplified_zone, "123456");
        let zone = zone.unwrap();
        assert_eq!(zone.packet.parsed().answers.len(), 9);

        println!("{}", zone.packet);
    }

    #[test]
    fn test_transform() {
        let simplified_zone = simplified_zone();
        let zone = SimpleZone::read(simplified_zone, "123456").unwrap();
        let packet = zone.packet.parsed();

        println!("{:#?}", packet.answers);
    }

    #[test]
    fn test_pkarr_records() {
        let records = "
@				IN 		A		37.27.13.182
pknames.p2p		IN 		A		37.27.13.182
www.pknames.p2p	IN		CNAME	pknames.p2p.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.
sub				IN		NS		ns.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.
ns				IN		A		95.217.214.181
cname			IN		CNAME	example.com.
_text			IN		TXT		hero=satoshi";

        let zone = SimpleZone::read(records.to_string(), "123456").unwrap();
        let packet = zone.packet.parsed();

        println!("{:#?}", packet.answers);
    }

    #[test]
    fn test_read_zone_txt1() {
        let raw_records = "foo   IN  TXT   \"key1=1\" \"key2=2\"";

        let zone = SimpleZone::read(raw_records.to_string(), "123456").unwrap();
        let packet = zone.packet.parsed();

        let entry = packet.answers.first().unwrap();

        match &entry.rdata {
            pkarr::dns::rdata::RData::TXT(txt) => {
                let value1 = txt.clone().attributes().get("key1").unwrap().clone().unwrap();
                assert_eq!(value1, "1");
                let value2 = txt.clone().attributes().get("key2").unwrap().clone().unwrap();
                assert_eq!(value2, "2");
            }
            _ => panic!("Expected TXT record, got {:?}", entry.rdata),
        }
    }

    #[test]
    fn test_read_zone_txt2() {
        let raw_records = "foo   IN  TXT   key=value";

        let zone = SimpleZone::read(raw_records.to_string(), "123456").unwrap();
        let packet = zone.packet.parsed();
        let entry = packet.answers.last().unwrap();

        match &entry.rdata {
            pkarr::dns::rdata::RData::TXT(txt) => {
                let value = txt.clone().attributes().get("key").unwrap().clone().unwrap();
                assert_eq!(value, "value");
            }
            _ => panic!("Expected TXT record, got {:?}", entry.rdata),
        }
    }

    #[test]
    fn test_read_zone_txt3() {
        let raw_records = "foo   IN  TXT   \"key=value\"";

        let zone = SimpleZone::read(raw_records.to_string(), "123456").unwrap();
        let packet = zone.packet.parsed();
        let entry = packet.answers.last().unwrap();

        match &entry.rdata {
            pkarr::dns::rdata::RData::TXT(txt) => {
                let value = txt.clone().attributes().get("key").unwrap().clone().unwrap();
                assert_eq!(value, "value");
            }
            _ => panic!("Expected TXT record, got {:?}", entry.rdata),
        }
    }
}


================================================
FILE: compose.yaml
================================================
services:
  pkdns:
    image: "synonymsoft/pkdns:latest"
    container_name: pkdns
    ports: 
      - "53:53/udp"
    restart: unless-stopped
    command: ["pkdns"]
    # Uncomment the volumes to persist the pkdns cache and config file on your disk permanently.
    # Change `/my/pkdns/folder/location` to a path on your machine where you want the data saved in.
    # volumes:
    #  - /my/pkdns/folder/location:/root/.pkdns


================================================
FILE: docs/dns-over-https.md
================================================
# DNS over HTTPS (DoH)

## What is DoH?

DNS over HTTPS (DoH) encrypts DNS queries by sending them over HTTPS instead of plain UDP or TCP. This enhances privacy and security by preventing eavesdropping and tampering of DNS traffic.

Popular web browsers like Firefox, Chrome, Brave, and Edge support DoH out of the box. It is a convenient way to enable Public Key Domains (PKD) in your browser without changing your system dns.

## Use A Hosted DoH Server In Your Browser

1. Pick a DNS-over-HTTPS URL from our public [servers.txt](../servers.txt) list.
2. Configure your browser. See [this guide](https://support.privadovpn.com/kb/article/848-how-to-enable-doh-on-your-browser/).
3. Test if everything is working with [http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/](http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/).



## Enable DoH In PKDNS

pkdns supports [RFC8484](https://datatracker.ietf.org/doc/html/rfc8484).

1. Start pkdns with `dns_over_http_socket = "127.0.0.1:3000"` in pkdns.toml. This makes pkdns listen for HTTP (not HTTPS) requests on http://127.0.0.1/dns-query.
2. Use a reverse proxy like NGINX to add HTTPS to the DoH socket. See this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04).
3. Forward the nginx requests to pkdns. Example configuration:

```
location / {
	proxy_set_header X-Forwarded-For $remote_addr;
	proxy_pass http://127.0.0.1:3000;
}
```
4. Configure your browser with your new doh url.
5. Test if everything is working with [http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/](http://7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy/).





================================================
FILE: docs/dyn-dns.md
================================================
# DynDNS

DynDNS (Dynamic Domain Name System) is a service that allows users to assign a fixed domain name to a device with a dynamic IP address. This is particularly useful for home networks, servers, or remote devices that do not have a static IP from their Internet Service Provider (ISP).

Key Purposes:
- Remote Access – Enables access to home or office networks remotely using a consistent domain name.
- Hosting Services – Supports hosting websites, game servers, or other services from a dynamic IP.
- Simplified Configuration – Automatically updates DNS records when the IP address changes, avoiding manual reconfiguration.

Essentially, DynDNS bridges the gap between dynamic IP addresses and the need for reliable remote access.

## How does PKDNS Enable DynDNS?

While publishing the `pkarr.zone` with `pkdns-cli`, the cli replaces the variables `{external_ipv4}` and `{external_ipv6}` with your actual external IP address. In combination with publishing your Public Key Domain (PKD)
every 60 minutes, your PKD keeps pointing to the correct IP address.


See [cli/sample/pkarr.zone](../cli/sample/pkarr.zone) as an example to use your external IPv4 address and [this guide](https://medium.com/pubky/how-to-host-a-public-key-domain-website-v0-6-0-ubuntu-24-04-57e6f2cb6f77) on how to publish it.

================================================
FILE: docs/logging.md
================================================
# Logging

By default pkdns stays silent. Use `--verbose` to make pkdns log all queries.

## Advanced

The log output can be adjusted with the environment variable `RUST_LOG`. This is either done by setting the log level directly (`RUST_LOG=debug`) or by setting the log level for specific modules.

Examples:

- `RUST_LOG=pkdns=trace` will make pkdns very chatty.
- `RUST_LOG=mainline=debug` will display mainline DHT logs.

These can also be combined: `RUST_LOG=pkdns=trace,mainline=trace`.

### Interesting Logs

- `RUST_LOG=pkdns=trace` Investigate pkdns.
- `RUST_LOG=pkdns=debug,pkarr=debug,mainline=debug` Investigate the mainline DHT.



================================================
FILE: rustfmt.toml
================================================
max_width = 120

================================================
FILE: server/Cargo.toml
================================================
[package]
name = "pkdns"
version = "0.7.1"
authors = ["SeverinAlexB <severin@synonym.to>"]
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ctrlc = "3.4.2"
pkarr = { version = "3.8.0"}
zbase32 = "0.1.2"
clap = { version = "4.4.18", features = ["derive"] }
chrono = "0.4.33"
tokio = { version = "1.36.0", features = ["full"] }
async-trait = "0.1.77"
anyhow = "1.0.79"
tracing = "0.1.40"
tracing-subscriber = {version = "0.3.18", features = ["smallvec", "fmt", "ansi", "tracing-log", "std", "env-filter"]}
rustdns = "0.4.0"
moka = { version = "0.12.8", features = ["future"] }

dyn-clone = "1.0.16"
thiserror = "1.0.56"
governor = "0.7.0"
axum = { version = "0.7.9", features = ["tokio"]}
axum-extra = { version = "0.9.6", features = ["typed-header"] }
tower-http = { version = "0.6.2", features = ["cors"] }
serde = {version = "1.0.216", features = ["derive"]}
base64 = "0.22.1"
toml = "0.8.19"
dirs = "5.0.1"
once_cell = "1.20.2"
rand = "0.8"
self_cell = "1.1.0"
tempfile = "3.20.0"


[dev-dependencies]
axum-test = "16.4.1"
tracing-test = "0.2.5"



================================================
FILE: server/config.sample.toml
================================================
# PKDNS configuration file
# More information on https://github.com/pubky/pkdns/server/sample-config.toml

[general]
# DNS UDP socket that pkdns is listening on.
socket = "0.0.0.0:53"

# DNS server that pkdns is falling back to for regular ICANN queries.
forward = "8.8.8.8:53"

# [EXPERIMENTAL] Enables DNS over HTTP on the given socket. Default: Disabled. More info https://github.com/pubky/pkdns/blob/master/docs/dns-over-https.md
dns_over_http_socket = "127.0.0.1:3000"

# Verbose logging. See https://github.com/pubky/pkdns/blob/master/docs/logging.md
verbose = false

[dns]
# Minimum number of seconds a value is cached for before being refreshed.
min_ttl = 60

# Maximum number of seconds before a cached value gets auto-refreshed. Set to 0 to prevent caching.
max_ttl = 86400

# Maximum number of queries per second one IP address can make before it is rate limited. 0 is disabled.
query_rate_limit = 100

# Short term burst size of the query-rate-limit. 0 is disabled.
query_rate_limit_burst = 200

# Disables ANY queries by silently dropping them. This is used to protect against DNS amplification attacks.
disable_any_queries = false

# ICANN response cache size in megabytes.
icann_cache_mb = 100

# Maximum recursion depth
max_recursion_depth = 15

# [dht]
# Maximum size of the pkarr packet cache in megabytes.
dht_cache_mb = 100

# Maximum number of queries per second one IP address can make to the DHT before it is rate limited. 0 is disabled.
dht_query_rate_limit = 5

# Short term burst size of the dht-rate-limit. 0 is disabled.
dht_query_rate_limit_burst = 25

# Optional Top Level Domain for public key domains. Set to "" to disable.
top_level_domain = "key"


================================================
FILE: server/pkdns.service
================================================
# https://github.com/pubky/pkdns/blob/master/server/pkdns.service
[Unit]
Description=pkdns - Self-Sovereign And Censorship-Resistant Domain Names
After=network-online.target

[Service]
# Update the binary path. Add --verbose to the command if you want to have more insights.
ExecStart=/usr/local/bin/pkdns
Environment="RUST_BACKTRACE=full"
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target


================================================
FILE: server/src/app_context.rs
================================================
use crate::config::{ConfigToml, DataDir};

#[derive(Debug, Clone, Default)]
pub struct AppContext {
    pub config: ConfigToml,
}

impl AppContext {
    pub fn from_data_dir(data_dir: impl DataDir) -> Result<Self, anyhow::Error> {
        data_dir.ensure_data_dir_exists_and_is_writable()?;
        let config = data_dir.read_or_create_config_file()?;
        Ok(Self { config })
    }

    #[cfg(test)]
    pub fn test() -> Self {
        Self {
            config: ConfigToml::test(),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::config::MockDataDir;

    use super::*;

    #[test]
    fn test_from_data_dir() {
        let data_dir = MockDataDir::test();
        let _ = AppContext::from_data_dir(data_dir).unwrap();
    }
}


================================================
FILE: server/src/config/config_file.rs
================================================
use serde::{Deserialize, Deserializer, Serialize};
use std::{fs, net::SocketAddr, num::NonZeroU64, path::Path};

use crate::config::TopLevelDomain;

/// Error that can occur when reading a configuration file.
#[derive(Debug, thiserror::Error)]
pub enum ConfigReadError {
    /// The file did not exist or could not be read.
    #[error("config file not found: {0}")]
    NotFound(#[from] std::io::Error),
    /// The TOML was syntactically invalid.
    #[error("config file is not valid TOML: {0}")]
    NotValid(#[from] toml::de::Error),
}

/// Example configuration file
pub const SAMPLE_CONFIG: &str = include_str!("../../config.sample.toml");

#[derive(Debug, Deserialize, Clone, Default)]
pub struct ConfigToml {
    #[serde(default)]
    pub general: General,
    #[serde(default)]
    pub dns: Dns,
    #[serde(default)]
    pub dht: Dht,
}

impl ConfigToml {
    /// Example configuration file as a string.
    pub fn sample() -> String {
        SAMPLE_CONFIG.to_string()
    }

    /// Render the embedded sample config but comment out every value,
    /// producing a handy template for end-users.
    pub fn commented_out_sample() -> String {
        SAMPLE_CONFIG
            .lines()
            .map(|line| {
                let trimmed = line.trim_start();
                let is_comment = trimmed.starts_with('#');
                if !is_comment && !trimmed.is_empty() {
                    format!("# {}", line)
                } else {
                    line.to_string()
                }
            })
            .collect::<Vec<String>>()
            .join("\n")
    }

    /// Read and parse a configuration file.
    ///
    /// # Arguments
    /// * `path` - The path to the TOML configuration file
    ///
    /// # Returns
    /// * `Result<ConfigToml>` - The parsed configuration or an error if reading/parsing fails
    pub fn from_file(path: impl AsRef<Path>) -> Result<Self, ConfigReadError> {
        let raw = fs::read_to_string(path)?;
        let config: ConfigToml = toml::from_str(&raw)?;
        Ok(config)
    }

    #[cfg(test)]
    pub fn test() -> Self {
        let mut config = Self::default();
        config.general.dns_over_http_socket = None;
        config.general.socket = "0.0.0.0:0".parse().expect("Is always be a valid socket address");
        config
    }
}

impl TryFrom<&str> for ConfigToml {
    type Error = ConfigReadError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let config: ConfigToml = toml::from_str(value)?;
        Ok(config)
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct General {
    #[serde(default = "default_socket")]
    pub socket: SocketAddr,

    #[serde(default = "default_forward")]
    pub forward: SocketAddr,

    #[serde(default = "default_none")]
    pub dns_over_http_socket: Option<SocketAddr>,

    #[serde(default = "default_false")]
    pub verbose: bool,
}

impl Default for General {
    fn default() -> Self {
        Self {
            socket: default_socket(),
            forward: default_forward(),
            verbose: default_false(),
            dns_over_http_socket: default_none(),
        }
    }
}

fn default_socket() -> SocketAddr {
    "0.0.0.0:53".parse().unwrap()
}

fn default_forward() -> SocketAddr {
    "8.8.8.8:53".parse().unwrap()
}

fn default_false() -> bool {
    false
}

fn default_none() -> Option<SocketAddr> {
    None
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Dns {
    #[serde(default = "default_min_ttl")]
    pub min_ttl: u64,

    #[serde(default = "default_max_ttl")]
    pub max_ttl: u64,

    #[serde(default = "default_query_rate_limit")]
    pub query_rate_limit: u32,

    #[serde(default = "default_query_rate_limit_burst")]
    pub query_rate_limit_burst: u32,

    #[serde(default = "default_false")]
    pub disable_any_queries: bool,

    #[serde(default = "default_icann_cache_mb")]
    pub icann_cache_mb: u64,

    #[serde(default = "default_max_recursion_depth")]
    pub max_recursion_depth: u8,
}

impl Default for Dns {
    fn default() -> Self {
        Self {
            min_ttl: default_min_ttl(),
            max_ttl: default_max_ttl(),
            query_rate_limit: default_query_rate_limit(),
            query_rate_limit_burst: default_query_rate_limit_burst(),
            disable_any_queries: default_false(),
            icann_cache_mb: default_icann_cache_mb(),
            max_recursion_depth: default_max_recursion_depth(),
        }
    }
}

fn default_min_ttl() -> u64 {
    60
}

fn default_max_ttl() -> u64 {
    86400
}

fn default_query_rate_limit() -> u32 {
    100
}

fn default_query_rate_limit_burst() -> u32 {
    200
}

fn default_icann_cache_mb() -> u64 {
    100
}

fn default_max_recursion_depth() -> u8 {
    15
}

#[derive(Debug, Deserialize, Clone)]
pub struct Dht {
    #[serde(default = "default_cache_mb")]
    pub dht_cache_mb: NonZeroU64,
    #[serde(default = "default_dht_rate_limit")]
    pub dht_query_rate_limit: u32,
    #[serde(default = "default_dht_rate_limit_burst")]
    pub dht_query_rate_limit_burst: u32,
    #[serde(
        default = "default_top_level_domain",
        deserialize_with = "deserialize_top_level_domain"
    )]
    pub top_level_domain: Option<TopLevelDomain>,
}

fn deserialize_top_level_domain<'de, D>(deserializer: D) -> Result<Option<TopLevelDomain>, D::Error>
where
    D: Deserializer<'de>,
{
    let value = Option::<String>::deserialize(deserializer)?;
    if let Some(tld) = &value {
        if tld.is_empty() {
            return Ok(None);
        }
    }
    Ok(value.map(TopLevelDomain::new))
}

fn default_cache_mb() -> NonZeroU64 {
    NonZeroU64::new(100).expect("100 is a valid non-zero u64")
}

fn default_dht_rate_limit() -> u32 {
    5
}

fn default_dht_rate_limit_burst() -> u32 {
    25
}

fn default_top_level_domain() -> Option<TopLevelDomain> {
    Some(TopLevelDomain::new("key".to_string()))
}

impl Default for Dht {
    fn default() -> Self {
        Self {
            dht_cache_mb: default_cache_mb(),
            dht_query_rate_limit: default_dht_rate_limit(),
            dht_query_rate_limit_burst: default_dht_rate_limit_burst(),
            top_level_domain: default_top_level_domain(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_sample_config() {
        let _: ConfigToml = toml::from_str(SAMPLE_CONFIG).expect("Sample config must be parseble");
    }

    #[test]
    fn test_commented_out_sample() {
        let commented_out = ConfigToml::commented_out_sample();
        println!("{commented_out}");
    }

    #[test]
    fn test_default_config_top_level_domain() {
        let config_str = "[dht]\ntop_level_domain = \"\"";
        let config = ConfigToml::try_from(config_str).unwrap();
        assert!(config.dht.top_level_domain.is_none());

        let config_str = "[dht]\ntop_level_domain = \"test\"";
        let config = ConfigToml::try_from(config_str).unwrap();
        assert_eq!(config.dht.top_level_domain.unwrap().0, "test".to_string());
    }
}


================================================
FILE: server/src/config/data_dir.rs
================================================
use dyn_clone::DynClone;
use std::path::Path;

use crate::config::ConfigToml;

/// A trait for the data directory.
/// Used to abstract the data directory from the rest of the code.
///
/// To create a real dir and a test dir.
pub trait DataDir: std::fmt::Debug + DynClone + Send + Sync {
    /// Returns the path to the data directory.
    fn path(&self) -> &Path;
    /// Makes sure the data directory exists.
    /// Create the directory if it doesn't exist.
    fn ensure_data_dir_exists_and_is_writable(&self) -> anyhow::Result<()>;

    /// Reads the config file from the data directory.
    /// Creates a default config file if it doesn't exist.
    fn read_or_create_config_file(&self) -> anyhow::Result<ConfigToml>;
}

dyn_clone::clone_trait_object!(DataDir);


================================================
FILE: server/src/config/mock_data_dir.rs
================================================
use std::path::Path;

use super::DataDir;

/// Mock data directory for testing.
///
/// It uses a temporary directory to store all data in. The data is removed as soon as the object is dropped.
///

#[derive(Debug, Clone)]
pub struct MockDataDir {
    pub(crate) temp_dir: std::sync::Arc<tempfile::TempDir>,
    /// The configuration for the homeserver.
    pub config_toml: super::ConfigToml,
}

impl MockDataDir {
    /// Create a new DataDirMock with a temporary directory.
    ///
    /// If keypair is not provided, a new one will be generated.
    pub fn new(config_toml: super::ConfigToml) -> anyhow::Result<Self> {
        Ok(Self {
            temp_dir: std::sync::Arc::new(tempfile::TempDir::new()?),
            config_toml,
        })
    }

    /// Creates a mock data directory with a config and keypair appropriate for testing.
    pub fn test() -> Self {
        let config = super::ConfigToml::test();
        Self::new(config).expect("failed to create MockDataDir")
    }
}

impl Default for MockDataDir {
    fn default() -> Self {
        Self::test()
    }
}

impl DataDir for MockDataDir {
    fn path(&self) -> &Path {
        self.temp_dir.path()
    }

    fn ensure_data_dir_exists_and_is_writable(&self) -> anyhow::Result<()> {
        Ok(()) // Always ok because this is validated by the tempfile crate.
    }

    fn read_or_create_config_file(&self) -> anyhow::Result<super::ConfigToml> {
        Ok(self.config_toml.clone())
    }
}


================================================
FILE: server/src/config/mod.rs
================================================
mod config_file;
mod data_dir;
#[cfg(test)]
mod mock_data_dir;
mod persistent_data_dir;
mod top_level_domain;

pub use config_file::ConfigToml;
pub use data_dir::DataDir;
#[cfg(test)]
pub use mock_data_dir::MockDataDir;
pub use persistent_data_dir::PersistentDataDir;
pub use top_level_domain::TopLevelDomain;


================================================
FILE: server/src/config/persistent_data_dir.rs
================================================
use super::{data_dir::DataDir, ConfigToml};
use std::{
    io::Write,
    path::{Path, PathBuf},
};

/// The data directory for the homeserver.
///
/// This is the directory that will store the homeservers data.
///
#[derive(Debug, Clone)]
pub struct PersistentDataDir {
    expanded_path: PathBuf,
}

impl PersistentDataDir {
    /// Creates a new data directory.
    /// `path` will be expanded to the home directory if it starts with "~".
    pub fn new(path: PathBuf) -> Self {
        Self {
            expanded_path: Self::expand_home_dir(path),
        }
    }

    /// Expands the data directory to the home directory if it starts with "~".
    /// Return the full path to the data directory.
    fn expand_home_dir(path: PathBuf) -> PathBuf {
        let path = match path.to_str() {
            Some(path) => path,
            None => {
                // Path not valid utf-8 so we can't expand it.
                return path;
            }
        };

        if path.starts_with("~/") {
            if let Some(home) = dirs::home_dir() {
                let without_home = path.strip_prefix("~/").expect("Invalid ~ prefix");
                let joined = home.join(without_home);
                return joined;
            }
        }
        PathBuf::from(path)
    }

    /// Returns the config file path in this directory.
    pub fn get_config_file_path(&self) -> PathBuf {
        self.expanded_path.join("config.toml")
    }

    fn write_sample_config_file(&self) -> anyhow::Result<()> {
        let config_string = ConfigToml::commented_out_sample();
        let config_file_path = self.get_config_file_path();
        let mut config_file = std::fs::File::create(config_file_path)?;
        config_file.write_all(config_string.as_bytes())?;
        Ok(())
    }
}

impl Default for PersistentDataDir {
    fn default() -> Self {
        Self::new(PathBuf::from("~/.pubky"))
    }
}

impl DataDir for PersistentDataDir {
    /// Returns the full path to the data directory.
    fn path(&self) -> &Path {
        &self.expanded_path
    }

    /// Makes sure the data directory exists.
    /// Create the directory if it doesn't exist.
    fn ensure_data_dir_exists_and_is_writable(&self) -> anyhow::Result<()> {
        std::fs::create_dir_all(&self.expanded_path)?;

        // Check if we can write to the data directory
        let test_file_path = self.expanded_path.join("test_write_f2d560932f9b437fa9ef430ba436d611"); // random file name to not conflict with anything
        std::fs::write(test_file_path.clone(), b"test")
            .map_err(|err| anyhow::anyhow!("Failed to write to data directory: {}", err))?;
        std::fs::remove_file(test_file_path)
            .map_err(|err| anyhow::anyhow!("Failed to write to data directory: {}", err))?;
        Ok(())
    }

    /// Reads the config file from the data directory.
    /// Creates a default config file if it doesn't exist.
    fn read_or_create_config_file(&self) -> anyhow::Result<ConfigToml> {
        let config_file_path = self.get_config_file_path();
        if !config_file_path.exists() {
            self.write_sample_config_file()?;
        }
        let config = ConfigToml::from_file(config_file_path)?;
        Ok(config)
    }
}

#[cfg(test)]
mod tests {
    use std::io::Write;

    use super::*;
    use tempfile::TempDir;

    /// Test that the home directory is expanded correctly.
    #[test]
    pub fn test_expand_home_dir() {
        let data_dir = PersistentDataDir::new(PathBuf::from("~/.pkdns"));
        let homedir = dirs::home_dir().unwrap();
        let expanded_path = homedir.join(".pkdns");
        assert_eq!(data_dir.expanded_path, expanded_path);
    }

    /// Test that the data directory is created if it doesn't exist.
    #[test]
    pub fn test_ensure_data_dir_exists_and_is_accessible() {
        let temp_dir = TempDir::new().unwrap();
        let test_path = temp_dir.path().join(".pkdns");
        let data_dir = PersistentDataDir::new(test_path.clone());

        data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
        assert!(test_path.exists());
        data_dir.read_or_create_config_file().unwrap();
        assert!(data_dir.get_config_file_path().exists());
        // temp_dir will be automatically cleaned up when it goes out of scope
    }

    #[test]
    pub fn test_get_default_config_file_path_exists() {
        let temp_dir = TempDir::new().unwrap();
        let test_path = temp_dir.path().join(".pkdns");
        let data_dir = PersistentDataDir::new(test_path.clone());
        data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
        let config_file_path = data_dir.get_config_file_path();
        assert!(!config_file_path.exists()); // Should not exist yet

        let mut config_file = std::fs::File::create(config_file_path.clone()).unwrap();
        config_file.write_all(b"test").unwrap();
        assert!(config_file_path.exists()); // Should exist now
                                            // temp_dir will be automatically cleaned up when it goes out of scope
    }

    #[test]
    pub fn test_read_or_create_config_file() {
        let temp_dir = TempDir::new().unwrap();
        let test_path = temp_dir.path().join(".pkdns");
        let data_dir = PersistentDataDir::new(test_path.clone());
        data_dir.ensure_data_dir_exists_and_is_writable().unwrap();
        let _ = data_dir.read_or_create_config_file().unwrap(); // Should create a default config file
        assert!(data_dir.get_config_file_path().exists());

        let _ = data_dir.read_or_create_config_file().unwrap(); // Should read the existing file
        assert!(data_dir.get_config_file_path().exists());
    }

    #[test]
    pub fn test_read_or_create_config_file_dont_override_existing_file() {
        let temp_dir = TempDir::new().unwrap();
        let test_path = temp_dir.path().join(".pkdns");
        let data_dir = PersistentDataDir::new(test_path.clone());
        data_dir.ensure_data_dir_exists_and_is_writable().unwrap();

        // Write a broken config file
        let config_file_path = data_dir.get_config_file_path();
        std::fs::write(config_file_path.clone(), b"test").unwrap();
        assert!(config_file_path.exists()); // Should exist now

        // Try to read the config file and fail because config is broken
        let read_result = data_dir.read_or_create_config_file();
        assert!(read_result.is_err());

        // Make sure the broken config file is still there
        let content = std::fs::read_to_string(config_file_path).unwrap();
        assert_eq!(content, "test");
    }
}


================================================
FILE: server/src/config/top_level_domain.rs
================================================
use pkarr::dns::{Name, Packet, Question, ResourceRecord};

/// Top Level Domain like .pkd with the capability
/// to remove and add the top level domain in queries/replies.
#[derive(Clone, Debug)]
pub struct TopLevelDomain(pub String);

impl TopLevelDomain {
    pub fn new(tld: String) -> Self {
        Self(tld)
    }

    pub fn label(&self) -> &str {
        &self.0
    }

    /// Checks if the query or reply contains a question that ends with a public key and the tld.
    pub fn question_ends_with_pubkey_tld(&self, packet: &Packet<'_>) -> bool {
        let question = packet.questions.first();
        if question.is_none() {
            return false;
        }
        let question = question.unwrap();
        self.name_ends_with_pubkey_tld(&question.qname)
    }

    /// Removes the top level domain from the query if it exists.
    /// Returns the new query and a flag if the tld has been removed.
    pub fn remove(&self, packet: &mut Packet<'_>) {
        let question = packet
            .questions
            .first()
            .expect("No question in query in pkarr_resolver.");
        let labels = question.qname.get_labels();

        let question_tld = labels
            .last()
            .expect("Question labels with no domain in pkarr_resolver")
            .to_string();

        if question_tld != self.0 {
            panic!(
                "Question tld {question_tld} does not match the given tld .{}",
                self.label()
            );
        }

        // let second_label = labels.get(labels.len() - 2).expect("Question should have 2 labels");
        // let parse_res: pkarr::PublicKey = parse_pkarr_uri(&second_label.to_string()).expect("Second label must be a pkarr public key");

        let slice = &labels[0..labels.len() - 1];
        let new_domain = slice
            .iter()
            .map(|label| label.to_string())
            .collect::<Vec<String>>()
            .join(".");

        let name = Name::new(&new_domain).unwrap().into_owned();
        let new_question = Question::new(name, question.qtype, question.qclass, question.unicast_response).into_owned();
        packet.questions = vec![new_question];
    }

    /// Checks if the name ends with a public key domain and the tld.
    pub fn name_ends_with_pubkey_tld(&self, name: &Name<'_>) -> bool {
        let labels = name.get_labels();
        if labels.len() < 2 {
            // Needs at least 2 labels. First: tld, second: publickey
            return false;
        }

        let question_tld = labels.last().unwrap().to_string();

        if question_tld != self.0 {
            return false;
        };

        let second_label = labels.get(labels.len() - 2).unwrap().to_string();
        let res: Result<pkarr::PublicKey, _> = second_label.try_into();
        res.is_ok()
    }

    /// Checks if the name ends with a public key domain
    pub fn name_ends_with_pubkey(&self, name: &Name<'_>) -> bool {
        let labels = name.get_labels();
        if labels.is_empty() {
            // Needs at least 2 labels. First: tld, second: publickey
            return false;
        }

        let question_tld = labels.last().unwrap().to_string();
        let res: Result<pkarr::PublicKey, _> = question_tld.try_into();
        res.is_ok()
    }

    /// Append the top level domain to the reply. Zones are stored without a tld on Mainline
    /// so we need to add it again here.
    pub fn add(&self, reply: &mut Packet<'_>) {
        // Append questions
        let mut new_questions = vec![];
        for question in reply.questions.iter() {
            if !self.name_ends_with_pubkey(&question.qname) {
                // Other question. Don't change.
                new_questions.push(question.clone());
                continue;
            };
            let original_domain = question.qname.to_string();
            let new_domain = format!("{original_domain}.{}", self.0);
            let new_name = Name::new(&new_domain).unwrap();
            let new_question =
                Question::new(new_name, question.qtype, question.qclass, question.unicast_response).into_owned();
            new_questions.push(new_question);
        }
        reply.questions = new_questions;
        // Append answers
        let mut new_answers = vec![];
        for answer in reply.answers.iter() {
            if !self.name_ends_with_pubkey(&answer.name) {
                // Other answer. Don't change.
                new_answers.push(answer.clone());
                continue;
            };
            let original_domain = answer.name.to_string();
            let new_domain = format!("{original_domain}.{}", self.0);
            let new_name = Name::new(&new_domain).unwrap();
            let new_answer = ResourceRecord::new(new_name, answer.class, answer.ttl, answer.rdata.clone()).into_owned();
            new_answers.push(new_answer);
        }
        reply.answers = new_answers;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pkarr::dns::rdata::A;

    fn create_query_with_domain(domain: &str) -> Vec<u8> {
        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);
        query.build_bytes_vec().unwrap()
    }

    fn create_reply_with_domain(domain: &str) -> Vec<u8> {
        let query = create_query_with_domain(domain);
        let mut packet = Packet::parse(&query).unwrap().into_reply();

        let rdata = pkarr::dns::rdata::RData::A(A { address: 0 });
        let answer1 = ResourceRecord::new(Name::new(domain).unwrap(), pkarr::dns::CLASS::IN, 60, rdata.clone());
        packet.answers.push(answer1);

        let answer2 = ResourceRecord::new(Name::new("example.com").unwrap(), pkarr::dns::CLASS::IN, 60, rdata);
        packet.answers.push(answer2);

        packet.build_bytes_vec().unwrap()
    }

    #[tokio::test]
    async fn is_pkarr_with_tld_valid_2_label() {
        let tld = TopLevelDomain::new("pkd".to_string());
        let domain = create_query_with_domain("7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd");
        let packet = Packet::parse(&domain).unwrap();
        assert!(tld.question_ends_with_pubkey_tld(&packet));
    }

    #[tokio::test]
    async fn is_pkarr_with_tld_valid_3_label() {
        let tld = TopLevelDomain::new("pkd".to_string());
        let domain = create_query_with_domain("test.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd");
        let packet = Packet::parse(&domain).unwrap();
        assert!(tld.question_ends_with_pubkey_tld(&packet));
    }

    #[tokio::test]
    async fn is_pkarr_with_tld_fail_1_label() {
        let tld = TopLevelDomain::new("pkd".to_string());
        let domain = create_query_with_domain("pkd");
        let packet = Packet::parse(&domain).unwrap();
        assert!(!tld.question_ends_with_pubkey_tld(&packet));
    }

    #[tokio::test]
    async fn is_pkarr_with_tld_fail_2_label_no_pubkey() {
        let tld = TopLevelDomain::new("nopubkey.pkd".to_string());
        let domain = create_query_with_domain("pkd");
        let packet = Packet::parse(&domain).unwrap();
        assert!(!tld.question_ends_with_pubkey_tld(&packet));
    }

    #[tokio::test]
    async fn is_pkarr_with_tld_fail_2_label_wrong_tld() {
        let tld = TopLevelDomain::new("7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.wrongpkd".to_string());
        let domain = create_query_with_domain("pkd");
        let packet = Packet::parse(&domain).unwrap();
        assert!(!tld.question_ends_with_pubkey_tld(&packet));
    }

    #[tokio::test]
    async fn remove_tld_success_2_labels() {
        let tld = TopLevelDomain::new("pkd".to_string());
        let domain = create_query_with_domain("7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd");
        let mut packet = Packet::parse(&domain).unwrap();
        tld.remove(&mut packet);
        // Rebuild packet from scratch
        let removed_query = packet.build_bytes_vec().unwrap();
        let packet = Packet::parse(&removed_query).unwrap();
        let question_domain = packet.questions.first().unwrap().qname.to_string();
        assert_eq!(question_domain, "7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy")
    }

    #[tokio::test]
    async fn remove_tld_success_3_labels() {
        let tld = TopLevelDomain::new("pkd".to_string());
        let domain = create_query_with_domain("test.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd");
        let mut packet = Packet::parse(&domain).unwrap();
        tld.remove(&mut packet);
        // Rebuild packet from scratch
        let removed_query = packet.build_bytes_vec().unwrap();
        let packet = Packet::parse(&removed_query).unwrap();
        let question_domain = packet.questions.first().unwrap().qname.to_string();
        assert_eq!(
            question_domain,
            "test.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy"
        )
    }

    #[tokio::test]
    async fn add_success_1_label() {
        let tld = TopLevelDomain::new("pkd".to_string());
        let domain = create_reply_with_domain("7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy");
        let mut packet = Packet::parse(&domain).unwrap();
        tld.add(&mut packet);
        // Rebuild packet from scratch
        let removed_query = packet.build_bytes_vec().unwrap();
        let packet = Packet::parse(&removed_query).unwrap();

        let question_domain = packet.questions.first().unwrap().qname.to_string();
        assert_eq!(
            question_domain,
            "7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd"
        );

        let answer1_domain = packet.answers.first().unwrap().name.to_string();
        assert_eq!(
            answer1_domain,
            "7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd"
        );
        let answer2_domain = packet.answers.get(1).unwrap().name.to_string();
        assert_eq!(answer2_domain, "example.com");
    }

    #[tokio::test]
    async fn add_success_2_label() {
        let tld = TopLevelDomain("pkd".to_string());
        let domain = create_reply_with_domain("test.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy");
        let mut packet = Packet::parse(&domain).unwrap();
        tld.add(&mut packet);
        // Rebuild packet from scratch
        let removed_query = packet.build_bytes_vec().unwrap();
        let packet = Packet::parse(&removed_query).unwrap();

        let question_domain = packet.questions.first().unwrap().qname.to_string();
        assert_eq!(
            question_domain,
            "test.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd"
        );

        let answer1_domain = packet.answers.first().unwrap().name.to_string();
        assert_eq!(
            answer1_domain,
            "test.7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy.pkd"
        );
        let answer2_domain = packet.answers.get(1).unwrap().name.to_string();
        assert_eq!(answer2_domain, "example.com");
    }
}


================================================
FILE: server/src/dns_over_https/mod.rs
================================================
mod server;

pub use server::run_doh_server;


================================================
FILE: server/src/dns_over_https/server.rs
================================================
//! RFC8484 Dns-over-http wireformat
//! https://datatracker.ietf.org/doc/html/rfc8484
//! The implementation works but could implement the standard more accurately,
//! especially when it comes to cache-control.

use crate::resolution::DnsSocket;
use axum::{
    body::Body,
    extract::{ConnectInfo, Query, State},
    http::{header, HeaderMap, Method, Response, StatusCode},
    response::IntoResponse,
    routing::{get, post},
    Router,
};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use pkarr::dns::Packet;
use std::{
    collections::HashMap,
    net::{IpAddr, SocketAddr},
    sync::Arc,
};
use tower_http::cors::{Any, CorsLayer};

/// Error prefix for web browsers so users actually
/// know what this url is about.
const ERROR_PREFIX: &str = "
Hello to pkdns DNS-over-HTTPS!
https://github.com/pubky/pkdns

Add this DNS url to your browsers to enable self-sovereign Public Key Domains (PKD).





dev:";

/// Validates the accept header.
/// Returns an error if the accept header is missing or not application/dns-message.
fn validate_accept_header(headers: &HeaderMap) -> Result<(), (StatusCode, String)> {
    let error: Result<(), (StatusCode, String)> = Err((
        StatusCode::BAD_REQUEST,
        format!("{ERROR_PREFIX} valid accept header missing"),
    ));
    let accept_header = match headers.get("accept") {
        Some(value) => value,
        None => return error,
    };

    let value_str = match accept_header.to_str() {
        Ok(value) => value,
        Err(_) => return error,
    };

    if value_str != "application/dns-message" {
        return error;
    }
    Ok(())
}

fn decode_dns_base64_packet(param: &String) -> Result<Vec<u8>, (StatusCode, String)> {
    let bytes = match URL_SAFE_NO_PAD.decode(param) {
        Ok(bytes) => bytes,
        Err(e) => {
            return Err((
                StatusCode::BAD_REQUEST,
                format!("Error decoding the dns base64 query parameter. {e}"),
            ));
        }
    };

    if let Err(e) = Packet::parse(&bytes) {
        tracing::info!("Failed to parse the base64 as a valid dns packet. {e}");
        return Err((
            StatusCode::BAD_REQUEST,
            format!("Failed to parse the base64 as a valid dns packet. {e}"),
        ));
    }
    Ok(bytes)
}

/// Extract lowest ttl of answer to set caching parameter
fn get_lowest_ttl(reply: &[u8]) -> u32 {
    const DEFAULT_VALUE: u32 = 300;

    let parsed_packet = match Packet::parse(reply) {
        Ok(parsed) => parsed,
        Err(_) => return DEFAULT_VALUE,
    };

    let val = parsed_packet
        .answers
        .iter()
        .map(|answer| answer.ttl)
        .reduce(std::cmp::min);

    val.unwrap_or(DEFAULT_VALUE)
}

/// Extracts the client IP for rate limiting.
/// Uses the "x-forwarded-for" header to support proxies.
/// If not available, uses the client IP directly.
fn extract_client_ip(request_addr: &SocketAddr, headers: &HeaderMap) -> IpAddr {
    let origin_ip = match headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
        Some(value) => value,
        None => return request_addr.ip(),
    };

    match origin_ip.parse() {
        Ok(ip) => ip,
        Err(e) => {
            tracing::debug!("Failed to parse the 'x-forwarded-for' header ip address. {e}");
            request_addr.ip()
        }
    }
}

async fn query_to_response(query: Vec<u8>, dns_socket: &mut DnsSocket, client_ip: IpAddr) -> Response<Body> {
    let reply = dns_socket.query_me_recursively_raw(query, Some(client_ip)).await;
    let lowest_ttl = get_lowest_ttl(&reply);

    let response = Response::builder()
        .status(StatusCode::OK)
        .header(header::CONTENT_TYPE, "application/dns-message")
        .header(header::CONTENT_LENGTH, reply.len())
        .header(header::CACHE_CONTROL, format!("max-age={lowest_ttl}"))
        .body(Body::from(reply))
        .expect("Failed to build response");

    response
}

async fn dns_query_get(
    headers: HeaderMap,
    Query(params): Query<HashMap<String, String>>,
    State(state): State<Arc<AppState>>,
    ConnectInfo(client_addr): ConnectInfo<SocketAddr>,
) -> Result<impl IntoResponse, impl IntoResponse> {
    let client_ip = extract_client_ip(&client_addr, &headers);
    validate_accept_header(&headers)?;

    let dns_param = match params.get("dns") {
        Some(value) => value,
        None => return Err((StatusCode::BAD_REQUEST, "valid dns query param required".to_string())),
    };
    let packet_bytes = decode_dns_base64_packet(dns_param)?;

    let mut socket = state.socket.clone();
    Ok(query_to_response(packet_bytes, &mut socket, client_ip).await)
}

async fn dns_query_post(
    headers: HeaderMap,
    State(state): State<Arc<AppState>>,
    ConnectInfo(client_addr): ConnectInfo<SocketAddr>,
    request: axum::http::Request<axum::body::Body>,
) -> Result<impl IntoResponse, impl IntoResponse> {
    let client_ip = extract_client_ip(&client_addr, &headers);
    validate_accept_header(&headers)?;

    let packet_bytes = match axum::body::to_bytes(request.into_body(), 65535usize).await {
        Ok(bytes) => bytes.to_vec(),
        Err(e) => return Err((StatusCode::BAD_REQUEST, e.to_string())),
    };

    let mut socket = state.socket.clone();
    Ok(query_to_response(packet_bytes, &mut socket, client_ip).await)
}

pub struct AppState {
    pub socket: DnsSocket,
}

fn create_app(dns_socket: DnsSocket) -> Router {
    let cors = CorsLayer::new()
        .allow_origin(Any)
        .allow_methods([Method::GET, Method::POST])
        .allow_headers(Any);

    Router::new()
        .route("/dns-query", get(dns_query_get))
        .route("/dns-query", post(dns_query_post))
        .layer(cors)
        .with_state(Arc::new(AppState { socket: dns_socket }))
}

pub async fn run_doh_server(addr: SocketAddr, dns_socket: DnsSocket) -> Result<SocketAddr, anyhow::Error> {
    let app = create_app(dns_socket);
    let listener = tokio::net::TcpListener::bind(addr).await?;
    let addr = listener.local_addr()?;
    tokio::spawn(async move {
        axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
            .await
            .unwrap();
    });
    Ok(addr)
}

#[cfg(test)]
mod tests {
    use std::net::SocketAddr;

    use crate::{app_context::AppContext, dns_over_https::server::create_app, resolution::DnsSocket};
    use axum_test::TestServer;
    use pkarr::dns::{Name, Packet, PacketFlag, Question};
    use tracing_test::traced_test;

    #[traced_test]
    #[tokio::test]
    async fn query_doh_wireformat_get() {
        // RFC8484 example https://datatracker.ietf.org/doc/html/rfc8484#section-4.1
        let context = AppContext::test();
        let socket = DnsSocket::new(&context).await.unwrap();
        let join_handle = socket.start_receive_loop();
        let app = create_app(socket);
        let server = TestServer::new(app.into_make_service_with_connect_info::<SocketAddr>()).unwrap();
        let base64 = "AAABAAABAAAAAAAAAWE-NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ";
        let response = server
            .get("/dns-query")
            .add_query_param("dns", base64)
            .add_header("accept", "application/dns-message")
            .await;

        response.assert_status_ok();
        assert_eq!(
            response.maybe_header("content-type").expect("content-type available"),
            "application/dns-message"
        );
        assert_eq!(
            response
                .maybe_header("content-length")
                .expect("content-length available"),
            "94"
        );

        let reply_bytes = response.into_bytes();
        let packet = Packet::parse(&reply_bytes).expect("Should be valid packet");
        // dbg!(&packet);
        assert_eq!(packet.answers.len(), 0);
        assert_eq!(packet.name_servers.len(), 0);
        assert_eq!(packet.additional_records.len(), 0);
        assert!(packet.has_flags(PacketFlag::RESPONSE));
        join_handle.send(()).unwrap();
    }

    #[traced_test]
    #[tokio::test]
    async fn query_doh_wireformat_post() {
        let context = AppContext::test();
        let socket = DnsSocket::new(&context).await.unwrap();
        let join_handle = socket.start_receive_loop();
        let app = create_app(socket);
        let server = TestServer::new(app.into_make_service_with_connect_info::<SocketAddr>()).unwrap();

        let mut query = Packet::new_query(50);
        let question = Question::new(
            Name::new_unchecked("example.com"),
            pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A),
            pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN),
            false,
        );
        query.questions.push(question);
        let bytes = query.build_bytes_vec().unwrap();
        let response = server
            .post("/dns-query")
            .add_header("accept", "application/dns-message")
            .bytes(bytes.into())
            .await;

        response.assert_status_ok();
        assert_eq!(
            response.maybe_header("content-type").expect("content-type available"),
            "application/dns-message"
        );

        let content_length_header = response
            .maybe_header("content-length")
            .expect("content-length available");
        let reply_bytes = response.into_bytes();
        assert_eq!(content_length_header, format!("{}", reply_bytes.len()));
        let packet = Packet::parse(&reply_bytes).expect("Should be valid packet");
        assert!(packet.answers.len() > 1);
        join_handle.send(()).unwrap();
    }

    #[tokio::test]
    async fn wrong_content_type() {
        // RFC8484 example https://datatracker.ietf.org/doc/html/rfc8484#section-4.1
        let context = AppContext::test();
        let socket = DnsSocket::new(&context).await.unwrap();
        socket.start_receive_loop();
        let app = create_app(socket);
        let server = TestServer::new(app.into_make_service_with_connect_info::<SocketAddr>()).unwrap();
        let base64 = "AAABAAABAAAAAAAAAWE-NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ";
        let response = server
            .get("/dns-query")
            .add_query_param("dns", base64)
            .add_header("accept", "application/wrong_type")
            .await;

        response.assert_status_bad_request();
    }
}


================================================
FILE: server/src/helpers.rs
================================================
use std::env;
use tracing::Level;
use tracing_subscriber::{filter::Targets, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

/**
 * Sets `RUST_BACKTRACE=1` as default so we always get a full stacktrace
 * on an error.
 */
pub(crate) fn set_full_stacktrace_as_default() {
    let key = "RUST_BACKTRACE";

    let is_value_already_set = env::var(key).is_ok();
    if is_value_already_set {
        return;
    }
    env::set_var(key, "1");
}

pub(crate) fn enable_logging(verbose: bool) {
    let key = "RUST_LOG";
    let value = match env::var(key) {
        Ok(val) => val,
        Err(_) => "".to_string(),
    };

    if !value.is_empty() {
        tracing_subscriber::fmt()
            .with_env_filter(EnvFilter::from_default_env())
            .init();
        tracing::info!("Use RUST_LOG={} env variable to set logging output.", value);
        if verbose {
            tracing::warn!("RUST_LOG= environment variable is already set. Ignore --verbose flag.")
        }
        return;
    }

    let regular_filter = tracing_subscriber::filter::Targets::new()
        .with_target("pkdns", Level::INFO)
        .with_target("mainline", Level::WARN);

    let verbose_filter = tracing_subscriber::filter::Targets::new()
        .with_target("pkdns", Level::DEBUG)
        .with_target("mainline", Level::WARN);

    let mut filter: Targets = regular_filter;
    if verbose {
        filter = verbose_filter;
    }

    tracing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer())
        .with(filter)
        .init();

    if verbose {
        tracing::info!("Verbose mode enabled.");
    }
}

/// Wait until the user hits CTRL+C
pub(crate) async fn wait_on_ctrl_c() {
    match tokio::signal::ctrl_c().await {
        Ok(()) => {}
        Err(err) => {
            eprintln!("Unable to listen for shutdown signal Ctrl+C: {}", err);
        }
    }
}


================================================
FILE: server/src/main.rs
================================================
use clap::Parser;
use dns_over_https::run_doh_server;
use helpers::{enable_logging, set_full_stacktrace_as_default, wait_on_ctrl_c};

use std::{error::Error, net::SocketAddr, path::PathBuf};

use crate::{app_context::AppContext, config::PersistentDataDir, resolution::DnsSocket};

mod app_context;
mod config;
mod dns_over_https;
mod helpers;
mod resolution;

#[derive(Parser, Debug)]
#[command(
    version,
    about = "pkdns - A DNS server for Public Key Domains (PDK) hosted on the Mainline DHT."
)]
struct Cli {
    /// ICANN fallback DNS server. Format: IP:Port. [default: 8.8.8.8:53]
    #[arg(short, long)]
    forward: Option<SocketAddr>,

    /// Show verbose output. [default: false]
    #[arg(short, long, action = clap::ArgAction::SetTrue)]
    verbose: Option<bool>,

    /// The base directory that contains pkdns's data, configuration file, etc.
    #[arg(short, long, default_value = "~/.pkdns")]
    pkdns_dir: PathBuf,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    set_full_stacktrace_as_default();
    let cli = Cli::parse();

    let data_dir = PersistentDataDir::new(cli.pkdns_dir);
    let mut app_context = AppContext::from_data_dir(data_dir)?;
    if let Some(verbose) = cli.verbose {
        app_context.config.general.verbose = verbose;
    }
    if let Some(forward) = cli.forward {
        app_context.config.general.forward = forward;
    }

    enable_logging(app_context.config.general.verbose);
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    tracing::info!("Starting pkdns v{VERSION}");
    tracing::debug!("Configuration:\n{:?}", app_context.config);
    tracing::info!("Forward ICANN queries to {}", app_context.config.general.forward);

    // Exit the main thread if anything panics
    let orig_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |panic_info| {
        // invoke the default handler and exit the process
        tracing::error!("Thread paniced. Stop main thread too.");
        orig_hook(panic_info);
        std::process::exit(1);
    }));

    let dns_socket = DnsSocket::new(&app_context).await?;

    let join_handle = dns_socket.start_receive_loop();

    tracing::info!(
        "Listening on {}. Waiting for Ctrl-C...",
        app_context.config.general.socket
    );

    if let Some(http_socket) = &app_context.config.general.dns_over_http_socket {
        let socket = run_doh_server(*http_socket, dns_socket).await?;
        tracing::info!("[EXPERIMENTAL] DNS-over-HTTP listening on http://{socket}/dns-query.");
    };

    wait_on_ctrl_c().await;
    println!();
    tracing::info!("Got it! Exiting...");
    join_handle
        .send(())
        .expect("Failed to send shutdown signal to DNS socket."); // If this fails, we panic as we are already trying to exit.

    Ok(())
}


================================================
FILE: server/src/resolution/dns_packets/mod.rs
================================================
mod parsed_packet;
mod parsed_query;

pub use parsed_packet::ParsedPacket;
pub use parsed_query::{ParseQueryError, ParsedQuery};


================================================
FILE: server/src/resolution/dns_packets/parsed_packet.rs
================================================
use anyhow::anyhow;
use chrono::format::Parsed;
use pkarr::dns::{Packet, PacketFlag};
use self_cell::self_cell;
use std::{fmt::Display, pin::Pin};

// Struct to hold the bytes and the packet in one place
// to avoid lifetimes aka a self-referencing struct.
self_cell!(
    pub struct Inner {
        owner: Vec<u8>,

        #[covariant]
        dependent: Packet,
    }

    impl {Debug}
);

impl Inner {
    /// Try to parse the packet from bytes
    pub fn try_from_bytes(bytes: Vec<u8>) -> Result<Self, pkarr::dns::SimpleDnsError> {
        Self::try_new(bytes, |bytes| Packet::parse(bytes))
    }

    /// Parsed DNS packet
    pub fn packet(&self) -> &Packet {
        self.borrow_dependent()
    }

    /// Raw bytes the packet is build with
    pub fn raw_bytes(&self) -> &Vec<u8> {
        self.borrow_owner()
    }
}

impl Clone for Inner {
    fn clone(&self) -> Self {
        let bytes = self.raw_bytes().clone();
        Self::try_from_bytes(bytes).unwrap()
    }
}

impl From<Inner> for Vec<u8> {
    fn from(val: Inner) -> Self {
        val.into_owner()
    }
}

/// Parses a dns packet without having to deal with life times
/// Both the raw bytes and the parsed struct is contained.
#[derive(Debug, Clone)]
pub struct ParsedPacket {
    pub inner: Inner,
}

impl ParsedPacket {
    pub fn new(raw_bytes: Vec<u8>) -> Result<Self, pkarr::dns::SimpleDnsError> {
        let inner = Inner::try_from_bytes(raw_bytes)?;
        Ok(Self { inner })
    }

    pub fn id(&self) -> u16 {
        self.parsed().id()
    }

    /// Parsed DNS packet
    pub fn parsed(&self) -> &Packet {
        self.inner.packet()
    }

    /// Raw bytes the packet is build with
    pub fn raw_bytes(&self) -> &Vec<u8> {
        self.inner.raw_bytes()
    }

    /// If this packet is a reply
    pub fn is_reply(&self) -> bool {
        self.parsed().has_flags(PacketFlag::RESPONSE)
    }

    /// If this packet is a reply
    pub fn is_query(&self) -> bool {
        !self.parsed().has_flags(PacketFlag::RESPONSE)
    }

    /// Create a REFUSED reply
    pub fn create_refused_reply(&self) -> Vec<u8> {
        let mut reply = Packet::new_reply(self.id());
        *reply.rcode_mut() = pkarr::dns::RCODE::Refused;
        reply.build_bytes_vec_compressed().unwrap()
    }

    /// Create SRVFAIL reply
    pub fn create_server_fail_reply(&self) -> Vec<u8> {
        let mut reply = Packet::new_reply(self.id());
        *reply.rcode_mut() = pkarr::dns::RCODE::ServerFailure;
        reply.build_bytes_vec_compressed().unwrap()
    }
}

impl From<ParsedPacket> for Vec<u8> {
    fn from(val: ParsedPacket) -> Self {
        val.inner.into()
    }
}

#[cfg(test)]
mod tests {
    use pkarr::dns::{Name, Packet, PacketFlag, Question};

    use super::*;
    #[tokio::test]
    async fn new() {
        let mut query = Packet::new_query(0);
        let qname = Name::new("example.com").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let parsed = ParsedPacket::new(raw_query).unwrap();
        assert_eq!(parsed.parsed().id(), 0);
    }
}


================================================
FILE: server/src/resolution/dns_packets/parsed_query.rs
================================================
use std::fmt::Display;

use super::ParsedPacket;
use anyhow::anyhow;
use pkarr::dns::{Packet, PacketFlag, Question, QTYPE};

#[derive(thiserror::Error, Debug)]
pub enum ParseQueryError {
    #[error("Dns packet parse error: {0}")]
    Parse(#[from] pkarr::dns::SimpleDnsError),

    #[error("Query validation error: {0}.")]
    Validation(#[from] anyhow::Error),
}

#[derive(Debug, Clone)]
pub struct ParsedQuery {
    pub packet: ParsedPacket,
}

impl ParsedQuery {
    /// Create a new parsed query.
    pub fn new(bytes: Vec<u8>) -> Result<Self, ParseQueryError> {
        let packet = ParsedPacket::new(bytes)?;
        let me = Self { packet };
        me.validate()?;
        Ok(me)
    }

    /// Checks if this packet is valid.
    fn validate(&self) -> Result<(), anyhow::Error> {
        if !self.packet.is_query() {
            return Err(anyhow!("Packet is not a query."));
        }
        let question = self.packet.parsed().questions.first();
        if question.is_none() {
            return Err(anyhow!("Packet without a question."));
        };
        let question = question.unwrap();
        let labels = question.qname.get_labels();
        if labels.is_empty() {
            return Err(anyhow!("Question with an empty qname."));
        };

        Ok(())
    }

    pub fn question(&self) -> &Question {
        self.packet.parsed().questions.first().unwrap()
    }

    /// If this query is ANY type which is often used for DNS amplification attacks.
    pub fn is_any_type(&self) -> bool {
        self.question().qtype == QTYPE::ANY
    }

    pub fn is_recursion_desired(&self) -> bool {
        self.packet.parsed().has_flags(PacketFlag::RECURSION_DESIRED)
    }
}

impl Display for ParsedQuery {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let question = self.question();
        let query_id = self.packet.parsed().id();
        let query_name = format!("{} {:?} query_id={query_id}", question.qname, question.qtype);
        write!(
            f,
            "{} {:?} {:?} id={query_id} rd={}",
            question.qname,
            question.qtype,
            question.qclass,
            self.packet.parsed().has_flags(PacketFlag::RECURSION_DESIRED)
        )
    }
}

impl TryFrom<ParsedPacket> for ParsedQuery {
    type Error = ParseQueryError;
    fn try_from(value: ParsedPacket) -> Result<Self, Self::Error> {
        let me = Self { packet: value };
        me.validate()?;
        Ok(me)
    }
}

impl From<ParsedQuery> for ParsedPacket {
    fn from(val: ParsedQuery) -> Self {
        val.packet
    }
}

#[cfg(test)]
mod tests {
    use pkarr::dns::{Name, Packet, PacketFlag, Question};

    use super::*;

    #[tokio::test]
    async fn new() {
        let mut query = Packet::new_query(0);
        let qname = Name::new("example.com").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let parsed = ParsedQuery::new(raw_query).unwrap();
    }

    #[tokio::test]
    async fn tryfrom_parsed_packet() {
        let mut query = Packet::new_query(0);
        let qname = Name::new("example.com").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let parsed = ParsedPacket::new(raw_query).unwrap();
        let parsed_query: ParsedQuery = parsed.try_into().unwrap();
    }
}


================================================
FILE: server/src/resolution/dns_socket.rs
================================================
#![allow(unused)]
use crate::{
    app_context::AppContext,
    resolution::{helpers::replace_packet_id, pkd::CustomHandlerError},
};
use rand::Rng;
use tracing_subscriber::fmt::format;

use super::{
    dns_packets::{ParsedPacket, ParsedQuery},
    pending_request::{PendingRequest, PendingRequestStore},
    pkd::PkarrResolver,
    query_id_manager::QueryIdManager,
    rate_limiter::{RateLimiter, RateLimiterBuilder},
    response_cache::IcannLruCache,
};
use pkarr::dns::{
    rdata::{RData, A, AAAA, NS},
    Packet, PacketFlag, SimpleDnsError, QTYPE, RCODE,
};
use std::{
    hash::{Hash, Hasher},
    num::NonZeroU64,
    thread::current,
};
use std::{
    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
    num::NonZeroU32,
    sync::Arc,
    time::{Duration, Instant},
};
use std::{
    net::{SocketAddrV4, SocketAddrV6},
    num,
};
use tokio::{
    net::UdpSocket,
    sync::{oneshot, RwLock},
    task::JoinHandle,
};
use tracing::Level;

/// Any error related to receiving and sending DNS packets on the UDP socket.
#[derive(thiserror::Error, Debug)]
pub enum DnsSocketError {
    #[error("Dns packet parse error: {0}")]
    Parse(#[from] SimpleDnsError),

    #[error(transparent)]
    IO(#[from] tokio::io::Error),

    #[error("Timeout. No answer received from forward server.")]
    ForwardTimeout(#[from] tokio::time::error::Elapsed),

    #[error("Rx receive error. {0}")]
    RxReceiedErr(#[from] oneshot::error::RecvError),
}

/**
 * DNS UDP socket
 */
#[derive(Debug, Clone)]
pub struct DnsSocket {
    socket: Arc<UdpSocket>,
    pending: PendingRequestStore,
    pkarr_resolver: PkarrResolver,
    icann_fallback: SocketAddr,
    id_manager: QueryIdManager,
    rate_limiter: Arc<RateLimiter>,
    disable_any_queries: bool,
    icann_cache: IcannLruCache,
    max_recursion_depth: u8,
}

impl DnsSocket {
    /// Default dns socket but with a random listening port. Made for testing.
    #[cfg(test)]
    pub async fn default_random_socket() -> tokio::io::Result<Self> {
        let listening: SocketAddr = "0.0.0.0:0".parse().expect("Is always be a valid socket address");
        let icann_resolver: SocketAddr = "8.8.8.8:53".parse().expect("Should always be a valid socket address");

        let mut context = AppContext::test();

        DnsSocket::new(&context).await
    }

    // Create a new DNS socket
    // TODO: Fix this too many arguments
    #[allow(clippy::too_many_arguments)]
    pub async fn new(context: &AppContext) -> tokio::io::Result<Self> {
        let socket = UdpSocket::bind(context.config.general.socket).await?;
        let limiter = RateLimiterBuilder::new()
            .max_per_second(context.config.dns.query_rate_limit)
            .burst_size(context.config.dns.query_rate_limit_burst);

        let pkarr_resolver = PkarrResolver::new(context).await;
        Ok(Self {
            socket: Arc::new(socket),
            pending: PendingRequestStore::new(),
            pkarr_resolver,
            icann_fallback: context.config.general.forward,
            id_manager: QueryIdManager::new(),
            rate_limiter: Arc::new(limiter.build()),
            disable_any_queries: context.config.dns.disable_any_queries,
            icann_cache: IcannLruCache::new(
                context.config.dns.icann_cache_mb,
                context.config.dns.min_ttl,
                context.config.dns.max_ttl,
            ),
            max_recursion_depth: context.config.dns.max_recursion_depth,
        })
    }

    fn is_recursion_available(&self) -> bool {
        self.max_recursion_depth > 1
    }

    // Send message to address
    pub async fn send_to(&self, buffer: &[u8], target: &SocketAddr) -> tokio::io::Result<usize> {
        self.socket.send_to(buffer, target).await
    }

    /// Starts the receive loop in the background.
    /// Returns the JoinHandle to stop the loop again.
    pub fn start_receive_loop(&self) -> oneshot::Sender<()> {
        let mut cloned = self.clone();
        let (tx, rx) = oneshot::channel::<()>();
        tokio::spawn(async move {
            let mut cancel = rx;
            loop {
                tokio::select! {
                    _ = &mut cancel => {
                        tracing::trace!("Stop UDP receive loop.");
                        break;
                    }
                    result = cloned.receive_datagram() => {
                        if let Err(err) = result {
                            tracing::error!("Error while trying to receive. {err}");
                        }
                    }
                }
            }
        });
        tx
    }

    async fn receive_datagram(&mut self) -> Result<(), DnsSocketError> {
        let mut buffer = [0; 1024];
        let (size, from) = self.socket.recv_from(&mut buffer).await?;

        let mut data = buffer.to_vec();
        if data.len() > size {
            data.drain((size + 1)..data.len());
        }

        let packet = ParsedPacket::new(data)?;

        let packet_id = packet.id();
        if let Some(query) = self.pending.remove_by_forward_id(&packet_id, &from) {
            tracing::trace!("Received response from forward server. Send back to client.");
            let _ = query.tx.send(packet.into()); // TODO: Handle error properly. Sometimes the channel is broken.
            return Ok(());
        };

        if packet.is_reply() {
            tracing::debug!(
                "Received reply without an associated query {:?}. forward_id={packet_id} Ignore.",
                packet
            );
            return Ok(());
        };

        // New query
        let query: ParsedQuery = match packet.try_into() {
            Ok(query) => query,
            Err(e) => {
                tracing::debug!("Failed to parse query {from}. id={packet_id}. {e} Drop.");
                return Ok(());
            }
        };

        if self.disable_any_queries && query.is_any_type() {
            tracing::debug!("Received ANY type question from {from}. id={packet_id}. Drop.");
            return Ok(());
        }

        let mut socket = self.clone();
        tokio::spawn(async move {
            let start = Instant::now();
            let reply = socket.query_me_recursively_with_log(&query, Some(from.ip())).await;
            socket.send_to(&reply, &from).await;
        });

        Ok(())
    }

    /// Queries recursively with a byte query. If the query can't be parsed, return a server fail.
    pub async fn query_me_recursively_raw(&mut self, query: Vec<u8>, from: Option<IpAddr>) -> Vec<u8> {
        let packet: ParsedPacket = match ParsedPacket::new(query) {
            Ok(packet) => packet,
            Err(e) => {
                tracing::trace!("Failed to parse query {e}. Drop");
                return vec![];
            }
        };

        match ParsedQuery::try_from(packet.clone()) {
            Ok(parsed) => self.query_me_recursively_with_log(&parsed, from).await,
            Err(e) => packet.create_server_fail_reply(),
        }
    }

    /// Queries recursively with a log.
    pub async fn query_me_recursively_with_log(&mut self, query: &ParsedQuery, from: Option<IpAddr>) -> Vec<u8> {
        let start = Instant::now();
        let reply = self.query_me_recursively(query, from).await;
        tracing::debug!("{query} processed within {}ms.", start.elapsed().as_millis());
        reply
    }

    /// Queries recursively. This is the main query function of this socket.
    async fn query_me_recursively(&mut self, query: &ParsedQuery, from: Option<IpAddr>) -> Vec<u8> {
        // Rate limit check
        if let Some(ip) = &from {
            if self.rate_limiter.check_is_limited_and_increase(ip) {
                tracing::trace!("Rate limited {}. query_id={}", query.packet.id(), ip);
                return query.packet.create_refused_reply();
            };
        }

        // Based on https://datatracker.ietf.org/doc/html/rfc1034#section-4.3.2

        // Original query coming from the client
        let original_client_query = query;

        // Main reply to the client
        let mut client_reply = original_client_query.packet.parsed().clone().into_reply();
        if self.is_recursion_available() {
            client_reply.set_flags(PacketFlag::RECURSION_AVAILABLE);
        } else {
            client_reply.remove_flags(PacketFlag::RECURSION_AVAILABLE);
        }
        let mut next_name_server: Option<SocketAddr> = None; // Name server to target. If none, falls back to default and DHT
        let mut next_raw_query: ParsedQuery = original_client_query.clone();
        for i in 0..self.max_recursion_depth {
            let current_query = next_raw_query.clone();
            tracing::trace!(
                "Recursive lookup {i}/{} NS:{next_name_server:?} - {current_query}",
                self.max_recursion_depth,
            );
            // println!("Recursive lookup {i}/{} NS:{next_name_server:?} - {:?}", self.max_recursion_depth, current_query.question());
            let reply = self.query_me_once(&current_query, from, next_name_server).await;
            next_name_server = None; // Reset target DNS
            let parsed_reply = Packet::parse(&reply).expect("Reply must be a valid dns packet.");

            if !self.is_recursion_available() {
                tracing::trace!("Recursion not available return.");
                return reply;
            }
            if !original_client_query.is_recursion_desired() {
                tracing::trace!("Recursion not desired. return.");
                return reply;
            }

            if parsed_reply.rcode() != RCODE::NoError {
                // Downstream server returned error.
                tracing::debug!(
                    "Downstream server returned error {:?} during recursion. Query: {current_query}",
                    parsed_reply.rcode()
                );
                *client_reply.rcode_mut() = parsed_reply.rcode();
                return client_reply.build_bytes_vec().unwrap();
            }

            if parsed_reply.answers.is_empty() && parsed_reply.name_servers.is_empty() {
                // No answers and NS received.
                tracing::warn!("Empty reply {current_query}");
                return client_reply.build_bytes_vec().unwrap();
            }

            let matching_answers_names: Vec<&pkarr::dns::ResourceRecord<'_>> = parsed_reply
                .answers
                .iter()
                .filter(|answer| answer.name == current_query.question().qname)
                .collect();

            // Check for direct matches
            let matching_answers_names_and_qtype: Vec<&pkarr::dns::ResourceRecord<'_>> = matching_answers_names
                .clone()
                .into_iter()
                .filter(|answer| answer.match_qtype(current_query.question().qtype))
                .collect();

            if !matching_answers_names_and_qtype.is_empty() {
                // We found answers matching the name and the type.
                // Copy everything over and return.
                tracing::trace!("Recursion final answer found.");

                for answer in parsed_reply.answers {
                    client_reply.answers.push(answer.into_owned());
                }
                for additional in parsed_reply.additional_records {
                    client_reply.additional_records.push(additional.into_owned());
                }
                for ns in parsed_reply.name_servers {
                    client_reply.name_servers.push(ns.into_owned());
                }
                return client_reply.build_bytes_vec().unwrap();
            }

            // No direct answer matches
            // Look for a CNAME
            let matching_cname = matching_answers_names
                .clone()
                .into_iter()
                .find(|answer| answer.match_qtype(QTYPE::TYPE(pkarr::dns::TYPE::CNAME)));

            if let Some(rr) = matching_cname {
                // Matching CNAME
                tracing::trace!("Recursion: Matching CNAME {rr:?}");
                if let pkarr::dns::rdata::RData::CNAME(val) = &rr.rdata {
                    // Clone CNAME answer to main reply.
                    client_reply.answers.push(rr.clone().into_owned());
                    // Replace question with the content of the cname
                    let mut question = current_query.question().clone().into_owned();
                    question.qname = val.0.clone();
                    let mut next_query = current_query.packet.parsed().clone();
                    next_query.questions = vec![question];
                    next_query.set_flags(PacketFlag::RECURSION_DESIRED);
                    next_raw_query = ParsedQuery::new(next_query.build_bytes_vec().unwrap()).unwrap();
                    continue;
                } else {
                    panic!("CNAME match failure. Shouldnt happen.")
                };
            };

            // Look for NS referals
            let ns_matches: Vec<&pkarr::dns::ResourceRecord<'_>> = parsed_reply
                .name_servers
                .iter()
                .filter(|rr| {
                    current_query.question().qname.is_subdomain_of(&rr.name)
                        || current_query.question().qname == rr.name
                })
                .collect();
            if ns_matches.is_empty() {
                // No NS matches either; Copy additional and return main reply.
                tracing::trace!("No direct and no ns matches");
                for additional in parsed_reply.additional_records {
                    client_reply.additional_records.push(additional.into_owned());
                }
                return client_reply.build_bytes_vec().unwrap();
            }

            tracing::trace!("NS matches. {parsed_reply:?}");
            let found_name_server = parsed_reply.name_servers.iter().find_map(|ns| {
                if let RData::NS(NS(ns_name)) = &ns.rdata {
                    let ns_a_record = parsed_reply.additional_records.iter().find(|rr| {
                        rr.name == *ns_name && rr.match_qtype(QTYPE::TYPE(pkarr::dns::TYPE::A))
                            || rr.match_qtype(QTYPE::TYPE(pkarr::dns::TYPE::AAAA))
                    });
                    ns_a_record?;
                    let ns_a_record = ns_a_record.unwrap();
                    let glued_ns_socket: SocketAddr = match ns_a_record.rdata {
                        RData::A(A { address }) => {
                            let ip = Ipv4Addr::from_bits(address);
                            let socket = SocketAddrV4::new(ip, 53);
                            socket.into()
                        }
                        RData::AAAA(AAAA { address }) => {
                            let ip = Ipv6Addr::from_bits(address);
                            let socket = SocketAddrV6::new(ip, 53, 0, 0);
                            socket.into()
                        }
                        _ => panic!("Prefiltered, shouldnt happen"),
                    };
                    Some(glued_ns_socket)
                } else {
                    None
                }
            });
            if let Some(socket) = &found_name_server {
                tracing::trace!("Found glued nameserver {socket}");
                next_name_server = found_name_server;
                continue;
            };

            // Unhandled NS response. Probably SOA. Return
            for ns in parsed_reply.name_servers.iter() {
                client_reply.name_servers.push(ns.clone().into_owned());
            }
            return client_reply.build_bytes_vec().unwrap();
        }

        // Max recursion exceeded
        tracing::debug!("Max recursion exceeded. {query}");
        original_client_query.packet.create_server_fail_reply()
    }

    /// Query this DNS for data once without recursion.
    /// from: Client ip used for rate limiting. None disables rate limiting
    /// target_dns: dns server to query. None falls back to the default fallback DNS
    async fn query_me_once(
        &mut self,
        query: &ParsedQuery,
        from: Option<IpAddr>,
        target_dns: Option<SocketAddr>,
    ) -> Vec<u8> {
        // Only try the DHT first if no target_dns is manually specified.
        if target_dns.is_none() {
            tracing::trace!("Trying to resolve the query with the custom handler.");
            let result = self.pkarr_resolver.resolve(query, from).await;
            if result.is_ok() {
                tracing::trace!("Custom handler resolved the query.");
                // All good. Handler handled the query
                return result.unwrap();
            }

            match result.unwrap_err() {
                CustomHandlerError::Unhandled => {
                    tracing::trace!("Custom handler rejected the query. {query}");
                }
                CustomHandlerError::Failed(err) => {
                    tracing::error!("Internal error {query}: {}", err);
                    return query.packet.create_server_fail_reply();
                }
                CustomHandlerError::RateLimited(ip) => {
                    tracing::error!("IP is rate limited {query}: {}", ip);
                    return query.packet.create_refused_reply();
                }
            };
        }

        // Forward to ICANN
        let dns_socket = target_dns.unwrap_or(self.icann_fallback);
        match self
            .forward_to_icann(query.packet.clone().raw_bytes(), dns_socket, Duration::from_secs(5))
            .await
        {
            Ok(reply) => reply,
            Err(e) => {
                tracing::warn!("Forwarding dns query failed. {e} {query}");
                query.packet.create_server_fail_reply()
            }
        }
    }

    /// Send dns request to configured forward server
    pub async fn forward(
        &mut self,
        query: &[u8],
        to: &SocketAddr,
        timeout: Duration,
    ) -> Result<Vec<u8>, DnsSocketError> {
        let packet = Packet::parse(query)?;
        let (tx, rx) = oneshot::channel::<Vec<u8>>();
        let forward_id = self.id_manager.get_next(to);
        let original_id = packet.id();
        tracing::trace!("Fallback to forward server {to:?}. orignal_id={original_id} forward_id={forward_id}");
        let request = PendingRequest {
            original_query_id: original_id,
            forward_query_id: forward_id,
            sent_at: Instant::now(),
            to: *to,
            tx,
        };

        let query = replace_packet_id(query, forward_id)?;

        self.pending.insert(request);
        self.send_to(&query, to).await?;

        // Wait on response
        let reply = tokio::time::timeout(timeout, rx).await??;
        let reply = replace_packet_id(&reply, original_id)?;

        Ok(reply)
    }

    /// Forward query to icann
    pub async fn forward_to_icann(
        &mut self,
        query: &[u8],
        dns_server: SocketAddr,
        timeout: Duration,
    ) -> Result<Vec<u8>, DnsSocketError> {
        // Check cache first before forwarding
        if let Ok(Some(item)) = self.icann_cache.get(query).await {
            let query_packet = Packet::parse(query)?;
            let new_response = replace_packet_id(&item.response, query_packet.id())?;
            return Ok(new_response);
        };

        let reply = self.forward(query, &dns_server, timeout).await?;
        // Store response in cache
        if let Err(e) = self.icann_cache.add(query.to_vec(), reply.clone()).await {
            tracing::warn!("Failed to add icann forward reply to cache. {e}");
        };

        Ok(reply)
    }

    // Extracts the id of the query
    fn extract_query_id(&self, query: &[u8]) -> Result<u16, SimpleDnsError> {
        Packet::parse(query).map(|packet| packet.id())
    }

    /// Create a REFUSED reply
    fn create_refused_reply(query_id: u16) -> Vec<u8> {
        let mut reply = Packet::new_reply(query_id);
        *reply.rcode_mut() = RCODE::Refused;
        reply.build_bytes_vec_compressed().unwrap()
    }

    /// Create SRVFAIL reply
    fn create_server_fail_reply(query_id: u16) -> Vec<u8> {
        let mut reply = Packet::new_reply(query_id);
        *reply.rcode_mut() = RCODE::ServerFailure;
        reply.build_bytes_vec_compressed().unwrap()
    }

    // pub async fn default() -> Result<Self, anyhow::Error> {
    //     let socket = UdpSocket::bind("0.0.0.0:53").await?;
    //     let config = get_global_config();
    //     Ok(Self {
    //         socket: Arc::new(socket),
    //         pending: PendingRequestStore::new(),
    //         pkarr_resolver: PkarrResolver::default().await,
    //         icann_fallback: "8.8.8.8:53".parse().unwrap(),
    //         id_manager: QueryIdManager::new(),
    //         rate_limiter: Arc::new(RateLimiterBuilder::new().build()),
    //         disable_any_queries: config.dns.disable_any_queries,
    //         icann_cache: IcannLruCache::new(100, config.dns.min_ttl, config.dns.max_ttl),
    //         max_recursion_depth: 5,
    //     })
    // }
}

#[cfg(test)]
mod tests {
    use crate::resolution::dns_packets::ParsedQuery;
    use crate::resolution::pkd::PkarrResolver;
    use pkarr::dns::rdata::{RData, NS};
    use pkarr::dns::{
        rdata::{A, CNAME},
        Name, Packet, PacketFlag, Question, ResourceRecord, RCODE,
    };
    use pkarr::{Client, Keypair, SignedPacket, Timestamp};
    use std::{
        net::{Ipv4Addr, SocketAddr},
        num::NonZeroU64,
        time::Duration,
    };
    use tracing_test::traced_test;

    use super::DnsSocket;

    async fn publish_domain() {
        // Public key csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto
        let seed = "a3kco17a6mqawd9jewgwijrd64gb1rmrer1zptxgire7buufk3hy";
        let decoded = zbase32::decode_full_bytes_str(seed).unwrap();
        let seed: [u8; 32] = decoded.try_into().unwrap();
        let pair = Keypair::from_secret_key(&seed);
        let pubkey = pair.public_key();
        let seed = pair.to_z32();

        let mut reply = Packet::new_reply(0);
        // Regular ICANN CNAME
        let cname_icann = ResourceRecord::new(
            Name::new("cname-icann").unwrap(),
            pkarr::dns::CLASS::IN,
            300,
            pkarr::dns::rdata::RData::CNAME(CNAME(Name::new("example.com").unwrap().into_owned())),
        );
        reply.answers.push(cname_icann);
        // PKD CNAME
        let cname_pkd = ResourceRecord::new(
            Name::new("cname-pkd").unwrap(),
            pkarr::dns::CLASS::IN,
            300,
            pkarr::dns::rdata::RData::CNAME(CNAME(
                Name::new("csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto")
                    .unwrap()
                    .into_owned(),
            )),
        );
        reply.answers.push(cname_pkd);
        // PKD CNAME that points on itself and therefore causes an infinite loop
        let cname_infinte = ResourceRecord::new(
            Name::new("cname-infinite").unwrap(),
            pkarr::dns::CLASS::IN,
            300,
            pkarr::dns::rdata::RData::CNAME(CNAME(
                Name::new("cname-infinite.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto")
                    .unwrap()
                    .into_owned(),
            )),
        );
        reply.answers.push(cname_infinte);
        // PKD CNAME that points on another PKD CNAME that points on a A
        let cname_pkd2 = ResourceRecord::new(
            Name::new("cname-pkd2").unwrap(),
            pkarr::dns::CLASS::IN,
            300,
            pkarr::dns::rdata::RData::CNAME(CNAME(
                Name::new("cname-pkd.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto")
                    .unwrap()
                    .into_owned(),
            )),
        );
        reply.answers.push(cname_pkd2);
        // Regular A entry that anchors the domain.
        let a = ResourceRecord::new(
            Name::new("").unwrap(),
            pkarr::dns::CLASS::IN,
            300,
            pkarr::dns::rdata::RData::A(A {
                address: Ipv4Addr::new(127, 0, 0, 1).to_bits(),
            }),
        );
        reply.answers.push(a);
        // Define BIND name server for the sub.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto zone.
        let ns = ResourceRecord::new(
            Name::new("ns.sub").unwrap(),
            pkarr::dns::CLASS::IN,
            300,
            pkarr::dns::rdata::RData::A(A {
                address: Ipv4Addr::new(95, 217, 214, 181).to_bits(),
            }),
        );
        reply.answers.push(ns);
        // Delegate sub.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto to the name server.
        let sub = ResourceRecord::new(
            Name::new("sub").unwrap(),
            pkarr::dns::CLASS::IN,
            300,
            pkarr::dns::rdata::RData::NS(NS(Name::new(
                "ns.sub.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto",
            )
            .unwrap())),
        );
        reply.answers.push(sub);
        let signed = SignedPacket::new(&pair, &reply.answers, Timestamp::now()).unwrap();
        let client = Client::builder().no_relays().build().unwrap();
        let _res = client.publish(&signed, None).await;
    }

    /// Create a new dns socket and query recursively.
    async fn resolve_query_recursively(query: Vec<u8>) -> Vec<u8> {
        let mut socket = DnsSocket::default_random_socket().await.unwrap();
        let join_handle = socket.start_receive_loop();
        let parsed_query = ParsedQuery::new(query).unwrap();
        let result = socket.query_me_recursively(&parsed_query, None).await;
        join_handle.send(());
        result
    }

    #[tokio::test]
    async fn recursion_cname_icann() {
        publish_domain().await;

        let mut query = Packet::new_query(0);
        let qname = Name::new("cname-icann.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        assert!(reply.answers.len() >= 2);
        let cname = reply.answers.first().unwrap();
        assert!(cname.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::CNAME)));
        let a = reply.answers.get(1).unwrap().clone().into_owned();
        assert!(a.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A)));
    }

    #[tokio::test]
    async fn recursion_cname_icann_with_tld() {
        publish_domain().await;

        let mut query = Packet::new_query(0);
        let qname = Name::new("cname-icann.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto.key").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        assert!(reply.answers.len() >= 2);
        let cname = reply.answers.first().unwrap();
        assert!(cname.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::CNAME)));
        let a = reply.answers.get(1).unwrap().clone().into_owned();
        assert!(a.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A)));
    }

    #[tokio::test]
    async fn recursion_cname_pkd() {
        // Single recursion CNAME
        publish_domain().await;
        let mut query = Packet::new_query(0);
        let qname = Name::new("cname-pkd.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        dbg!(&reply);
        assert_eq!(reply.answers.len(), 2);
        let cname = reply.answers.first().unwrap();
        assert!(cname.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::CNAME)));
        let a = reply.answers.get(1).unwrap().clone().into_owned();
        assert!(a.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A)));
    }

    #[tokio::test]
    async fn recursion_cname_pkd2() {
        // Double recursion CNAME
        publish_domain().await;

        let mut query = Packet::new_query(0);
        let qname = Name::new("cname-pkd2.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        assert_eq!(reply.answers.len(), 3);
        let cname1 = reply.answers.first().unwrap();
        assert!(cname1.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::CNAME)));
        let cname2 = reply.answers.get(1).unwrap();
        assert!(cname2.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::CNAME)));
        let a = reply.answers.get(2).unwrap().clone().into_owned();
        assert!(a.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A)));
    }

    #[tokio::test]
    async fn recursion_cname_infinite() {
        // Infinite recursion CNAME
        // Check max recursion depth
        publish_domain().await;

        let mut query = Packet::new_query(0);
        let qname = Name::new("cname-infinite.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        assert_eq!(reply.rcode(), RCODE::ServerFailure);
    }

    #[tokio::test]
    async fn recursion_not_found1() {
        // Check if the error is copied to
        publish_domain().await;

        let mut query = Packet::new_query(0);
        let qname = Name::new("osjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        // dbg!(&reply);
        assert_eq!(reply.rcode(), RCODE::NameError);
    }

    #[tokio::test]
    async fn recursion_not_found2() {
        publish_domain().await;
        let mut query = Packet::new_query(0);
        let qname = Name::new("yolo.example.com").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        assert_eq!(reply.rcode(), RCODE::NameError);
    }

    #[tokio::test]
    async fn recursion_ns_pkd() {
        // Single recursion with a delegated zone with an external name server
        // Domain is saved in the name server and not in the pkarr zone.
        publish_domain().await;
        let mut query = Packet::new_query(0);
        let qname = Name::new("sub.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let reply = Packet::parse(&raw_reply).unwrap();
        assert_eq!(reply.answers.len(), 1);
        let a = reply.answers.first().unwrap().clone().into_owned();
        assert!(a.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A)));
        assert_eq!(
            a.rdata,
            RData::A(A {
                address: Ipv4Addr::new(37, 27, 13, 182).to_bits()
            })
        );
    }

    #[tokio::test]
    async fn recursion_ns_soa_icann() {
        // NS SOA record with lots of cnames
        let mut query = Packet::new_query(0);
        let qname = Name::new("ap.lijit.com").unwrap();
        let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
        let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
        let question = Question::new(qname, qtype, qclass, false);
        query.questions = vec![question];
        query.set_flags(PacketFlag::RECURSION_DESIRED);
        let raw_query = query.build_bytes_vec_compressed().unwrap();

        let raw_reply = resolve_query_recursively(raw_query).await;
        let final_reply = Packet::parse(&raw_reply).unwrap();
        dbg!(&final_reply);
        assert!(!final_reply.answers.is_empty());
    }

    // TODO: tld support for NS referrals
    // #[tokio::test]
    // async fn recursion_ns_pkd_with_tld() {
    //     // Single recursion with a delegated zone with an external name server
    //     // Domain is saved in the name server and not in the pkarr zone.
    //     publish_domain().await;
    //     let mut query = Packet::new_query(0);
    //     let qname = Name::new("sub.csjbhp9jpbomwh3m5eyrj1py41m8sjpkzzqmzpj5madsi7sc4mto.key").unwrap();
    //     let qtype = pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A);
    //     let qclass = pkarr::dns::QCLASS::CLASS(pkarr::dns::CLASS::IN);
    //     let question = Question::new(qname, qtype, qclass, false);
    //     query.questions = vec![question];
    //     query.set_flags(PacketFlag::RECURSION_DESIRED);
    //     let raw_query = query.build_bytes_vec_compressed().unwrap();

    //     let raw_reply = resolve_query_recursively(raw_query).await;
    //     let reply = Packet::parse(&raw_reply).unwrap();
    //     assert_eq!(reply.answers.len(), 1);
    //     let a = reply.answers.get(0).unwrap().clone().into_owned();
    //     assert!(a.match_qtype(pkarr::dns::QTYPE::TYPE(pkarr::dns::TYPE::A)));
    //     assert_eq!(
    //         a.rdata,
    //         RData::A(A {
    //             address: Ipv4Addr::new(37, 27, 13, 182).to_bits()
    //         })
    //     );
    // }
}


================================================
FILE: server/src/resolution/helpers.rs
================================================
use pkarr::dns::{Packet, SimpleDnsError};

/// Replaces the id of a dns packet.
pub fn replace_packet_id(packet: &[u8], new_id: u16) -> Result<Vec<u8>, SimpleDnsError> {
    let mut cloned = packet.to_vec();
    let id_bytes = new_id.to_be_bytes();
    std::mem::replace(&mut cloned[0], id_bytes[0]);
    std::mem::replace(&mut cloned[1], id_bytes[1]);

    let parsed_packet = Packet::parse(&cloned)?;
    parsed_packet.build_bytes_vec()
}


================================================
FILE: server/src/resolution/mod.rs
================================================
#![allow(unused)]

/**
 * Basic module to process DNS queries with a UDP socket.
 * Allows to hook into the socket and process custom queries.
 */
mod dns_socket;
// mod dns_socket_builder;
mod helpers;
mod pending_request;
mod pkd;
mod query_id_manager;
mod rate_limiter;
mod response_cache;

mod dns_packets;

pub use dns_socket::{DnsSocket, DnsSocketError};
// pub use dns_socket_builder::DnsSocketBuilder;
pub use rate_limiter::{RateLimiter, RateLimiterBuilder};


================================================
FILE: server/src/resolution/pending_request.rs
================================================
#![allow(unused)]

use std::{
    collections::HashMap,
    net::SocketAddr,
    sync::{Arc, Mutex},
    time::Instant,
};

use tokio::sync::oneshot;

/// A pending request to a forward server.
#[derive(Debug)]
pub struct PendingRequest {
    /// Where the request was sent to
    pub to: SocketAddr,
    /// When the request was sent
    pub sent_at: Instant,
    /// The original query id coming from the client
    pub original_query_id: u16,
    /// The forward query id sent to the forward server
    pub forward_query_id: u16,
    /// The sender to send the response back to the client
    pub tx: oneshot::Sender<Vec<u8>>,
}

#[derive(Debug, Clone, Hash, PartialEq)]
struct PendingRequestKey {
    to: SocketAddr,
    forward_query_id: u16,
}

impl Eq for PendingRequestKey {}

/**
 * Thread safe pending request store.
 * Use `.clone()` to give each thread one store struct.
 * The data will stay shared.
 */
#[derive(Debug, Clone)]
pub struct PendingRequestStore {
    pending: Arc<Mutex<HashMap<PendingRequestKey, PendingRequest>>>,
}

impl PendingRequestStore {
    /// Insert a new pending request
    pub fn insert(&mut self, request: PendingRequest) {
        let mut locked = self.pending.lock().expect("Lock is always successful except when poisoned. If poisened it will be poisened forever. We panic here because we can't recover from this.");
        let key = PendingRequestKey {
            forward_query_id: request.forward_query_id,
            to: request.to,
        };
        locked.insert(key, request);
    }

    /// Remove a pending request by forward query id and from address
    pub fn remove_by_forward_id(&mut self, forward_query_id: &u16, from: &SocketAddr) -> Option<PendingRequest> {
        let mut locked = self.pending.lock().expect("Lock is always successful except when poisoned. If poisened it will be poisened forever. We panic here because we can't recover from this.");
        let key = PendingRequestKey {
            forward_query_id: *forward_query_id,
            to: *from,
        };
        locked.remove(&key)
    }

    pub fn new() -> Self {
        Self {
            pending: Arc::new(Mutex::new(HashMap::new())),
        }
    }
}


================================================
FILE: server/src/resolution/pkd/bootstrap_nodes.rs
================================================
use std::{
    net::{IpAddr, SocketAddr, UdpSocket},
    time::Duration,
};

use anyhow::anyhow;
use rustdns::{Class, Extension, Message, Resource, Type};

#[derive(Debug)]
pub(crate) struct DomainPortAddr {
    domain: &'static str,
    port: u16,
}

impl DomainPortAddr {
    pub const fn new(domain: &'static str, port: u16) -> Self {
        Self { domain, port }
    }
}

impl std::fmt::Display for DomainPortAddr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.domain, self.port)
    }
}

pub(crate) static DEFAULT_BOOTSTRAP_NODES: [DomainPortAddr; 4] = [
    DomainPortAddr::new("router.bittorrent.com", 6881),
    DomainPortAddr::new("dht.transmissionbt.com", 6881),
    DomainPortAddr::new("dht.libtorrent.org", 25401),
    DomainPortAddr::new("router.utorrent.com", 6881),
];

#[derive(Debug, thiserror::Error)]
pub enum MainlineBootstrapResolverError {
    #[error("Failed to resolve any of the boostrap node domains")]
    DomainResolutionFailed,

    #[error("Failed to create a network socket. {0}")]
    SocketError(#[from] std::io::Error),
}

/// Resolve the mainline dht boostrap nodes with a custom dns server.
/// Used because if pkdns is set as the system dns on the machine, it can't rely
/// on itself to resolve while starting.
pub(crate) struct MainlineBootstrapResolver {
    socket: UdpSocket,
}

impl MainlineBootstrapResolver {
    pub fn new(dns_server: SocketAddr) -> Result<Self, MainlineBootstrapResolverError> {
        let socket = UdpSocket::bind("0.0.0.0:0")?;
        socket.set_read_timeout(Some(Duration::new(3, 0)))?;
        socket.connect(dns_server)?;
        Ok(Self { socket })
    }

    /// Lookup a domain and return the first A record.
    fn lookup_domain(&self, domain: &str) -> Result<Option<IpAddr>, MainlineBootstrapResolverError> {
        let mut m = Message::default();
        m.add_question(domain, Type::A, Class::Internet);
        m.add_extension(Extension {
            // Optionally add a EDNS extension
            payload_size: 4096, // which supports a larger payload size.
            ..Default::default()
        });
        let question = m.to_vec()?;
        self.socket.send(&question)?;

        // Wait for a response from the DNS server.
        let mut resp = [0; 4096];
        let len = self.socket.recv(&mut resp)?;

        // Take the response bytes and turn it into another DNS Message.
        let reply = Message::from_slice(&resp[0..len])?;
        let first_answer = match reply.answers.first() {
            Some(answer) => answer,
            None => return Ok(None),
        };

        match first_answer.resource {
            Resource::A(val) => Ok(Some(IpAddr::V4(val))),
            _ => Ok(None),
        }
    }

    /// Lookup the domain of the boostrap node and return a SocketAddr.
    fn lookup(&self, boostrap_node: &DomainPortAddr) -> Result<Option<SocketAddr>, MainlineBootstrapResolverError> {
        let res = self.lookup_domain(boostrap_node.domain)?;
        Ok(res.map(|ip| SocketAddr::new(ip, boostrap_node.port)))
    }

    /// Lookup all the bootstrap nodes and return a list of SocketAddrs.
    pub fn get_bootstrap_nodes(&self) -> Result<Vec<SocketAddr>, MainlineBootstrapResolverError> {
        let mut addrs: Vec<SocketAddr> = vec![];
        for node in DEFAULT_BOOTSTRAP_NODES.iter() {
            match self.lookup(node) {
                Ok(Some(val)) => {
                    addrs.push(val);
                }
                Ok(None) => {
                    tracing::debug!("Failed to resolve the DHT bootstrap node domain {node}. No ip found.");
                }
                Err(err) => {
                    tracing::trace!("Failed to resolve the DHT bootstrap node domain {node}. {err}");
                }
            }
        }
        if !addrs.is_empty() {
            Ok(addrs)
        } else {
            Err(MainlineBootstrapResolverError::DomainResolutionFailed)
        }
    }

    /// Lookup all the bootstrap nodes and return a list of Strings.
    pub fn get_addrs(dns_server: &SocketAddr) -> Result<Vec<SocketAddr>, MainlineBootstrapResolverError> {
        let resolver = MainlineBootstrapResolver::new(*dns_server).unwrap();
        let addrs = resolver.get_bootstrap_nodes()?;
        Ok(addrs)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn query_domain() {
        let google_dns: SocketAddr = "8.8.8.8:53".parse().expect("valid addr");
        let resolver = MainlineBootstrapResolver::new(google_dns).unwrap();
        let res = resolver.lookup_domain("example.com").unwrap().expect("Valid ip");
    }

    #[tokio::test]
    async fn query_bootstrap_node() {
        let google_dns: SocketAddr = "8.8.8.8:53".parse().expect("valid addr");
        let node = DomainPortAddr::new("example.com", 6881);
        let resolver = MainlineBootstrapResolver::new(google_dns).unwrap();
        let res = resolver.lookup(&node).expect("Valid ip address resolved").unwrap();
        assert_eq!(res.port(), 6881);
    }

    #[tokio::test]
    async fn query_bootstrap_nodes() {
        let google_dns: SocketAddr = "8.8.8.8:53".parse().expect("valid addr");
        let resolver = MainlineBootstrapResolver::new(google_dns).unwrap();
        let addrs = resolver.get_bootstrap_nodes().unwrap();
        assert_eq!(addrs.len(), 4);
        assert_eq!(addrs.first().unwrap().to_string(), "67.215.246.10:6881");
    }
}


================================================
FILE: server/src/resolution/pkd/mod.rs
================================================
mod bootstrap_nodes;
mod pkarr_cache;
mod pkarr_resolver;
mod pubkey_parser;
mod query_matcher;

pub use pkarr_resolver::{CustomHandlerError, PkarrResolver, PkarrResolverError};
use pubkey_parser::parse_pkarr_uri;


================================================
FILE: server/src/resolution/pkd/pkarr_cache.rs
================================================
//!
//! Goal1: Cache things as long as possible to make any attack on the DHT unfeasible.
//! Goal2: Prevent attackers from overflowing the cache and evict values this way.

use std::time::{SystemTime, UNIX_EPOCH};

use moka::future::Cache;
use pkarr::{PublicKey, SignedPacket};

/**
 * Timestamp in seconds since UNIX_EPOCH
 */
fn get_timestamp_seconds() -> u64 {
    let start = SystemTime::now();
    let since_the_epoch = start.duration_since(UNIX_EPOCH).expect("Time went backwards");
    since_the_epoch.as_secs()
}

/**
 * Caches pkarr packets and not found pkarr packets.
 * Not found is important to avoid calling the DHT over and over again.
 */
#[derive(Clone, Debug)]
pub enum CacheItem {
    NotFound {
        public_key: PublicKey,
        /**
         * When the packet got added to the cache or cache got updated. Seconds timestamp since UNIX_EPOCH.
         */
        last_updated_at: u64,
    },
    Packet {
        packet: SignedPacket,
        /**
         * When the packet got added to the cache or cache got updated. Seconds timestamp since UNIX_EPOCH.
         */
        last_updated_at: u64,
    },
}

impl CacheItem {
    pub fn new_packet(packet: SignedPacket) -> Self {
        Self::Packet {
            packet,
            last_updated_at: get_timestamp_seconds(),
        }
    }

    pub fn new_not_found(pubkey: PublicKey) -> Self {
        Self::NotFound {
            public_key: pubkey,
            last_updated_at: get_timestamp_seconds(),
        }
    }

    /// Checks if this cache includes a signed packet.
    pub fn is_found(&self) -> bool {
        matches!(
            self,
            CacheItem::Packet {
                packet: _,
                last_updated_at: _
            }
        )
    }

    pub fn not_found(&self) -> bool {
        !self.is_found()
    }

    #[allow(dead_code)]
    pub fn is_packet(&self) -> bool {
        matches!(
            self,
            CacheItem::Packet {
                packet: _,
                last_updated_at: _
            }
        )
    }

    /**
     * Returns signed packet. Panics if not found.
     */
    pub fn unwrap(self) -> SignedPacket {
        if let CacheItem::Packet {
            packet,
            last_updated_at: _,
        } = self
        {
            packet
        } else {
            panic!("Can not unwrap CacheItem without a packet.")
        }
    }

    pub fn public_key(&self) -> PublicKey {
        match self {
            CacheItem::NotFound {
                public_key,
                last_updated_at: _,
            } => public_key.clone(),
            CacheItem::Packet {
                packet,
                last_updated_at: _,
            } => packet.public_key(),
        }
    }

    /**
     * Updates the cached_at timestamp to now.
     */
    pub fn refresh_updated_at(&mut self) {
        match self {
            CacheItem::NotFound {
                public_key: _,
                last_updated_at: cached_at,
            } => {
                *cached_at = get_timestamp_seconds();
            }
            CacheItem::Packet {
                packet: _,
                last_updated_at: cached_at,
            } => {
                *cached_at = get_timestamp_seconds();
            }
        }
    }

    /**
     * Timestamp given by the controller of the keypair. Basically a version number of the packet.
     * NotFound items always have a timestamp of 0.
     */
    pub fn controller_timestamp(&self) -> u64 {
        match self {
            CacheItem::NotFound {
                public_key: _,
                last_updated_at: _,
            } => 0,
            CacheItem::Packet {
                packet,
                last_updated_at: _,
            } => packet.timestamp().as_u64(),
        }
    }

    fn last_updated_at(&self) -> u64 {
        match self {
            CacheItem::NotFound {
                public_key: _,
                last_updated_at: cached_at,
            } => *cached_at,
            CacheItem::Packet {
                packet: _,
                last_updated_at: cached_at,
            } => *cached_at,
        }
    }

    /**
     * Lowest ttl of any anwser in seconds. Used to determine when to update the cache.
     * NotFound or packet with now answeres => None.
     */
    fn lowest_answer_ttl(&self) -> Option<u64> {
        match self {
            CacheItem::NotFound {
                public_key: _,
                last_updated_at: _,
            } => None,
            CacheItem::Packet {
                packet,
                last_updated_at: _,
            } => packet.all_resource_records().map(|answer| answer.ttl as u64).min(),
        }
    }

    /**
     * Size of the cached value in the memory.
     */
    pub fn memory_size(&self) -> usize {
        match self {
            CacheItem::NotFound {
                public_key: _,
                last_updated_at: _,
            } => {
                32 + 8 // Public key 32 + cached_at 8
            }
            CacheItem::Packet {
                packet,
                last_updated_at: _,
            } => packet.as_bytes().len() + 8,
        }
    }

    /**
     * When the next refresh of this cached element is needed.
     */
    pub fn next_refresh_needed_in_s(&self, min_ttl: u64, max_ttl: u64) -> u64 {
        let ttl = self.lowest_answer_ttl().unwrap_or(min_ttl);

        let ttl = if ttl < min_ttl { min_ttl } else { ttl };

        let ttl = if ttl > max_ttl { max_ttl } else { ttl };

        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_secs();

        let age_seconds = now - self.last_updated_at();
        ttl.saturating_sub(age_seconds)
    }
}

/**
 * LRU cache for packets.
 */
#[derive(Clone, Debug)]
pub struct PkarrPacketLruCache {
    cache: Cache<PublicKey, CacheItem>, // Moka Cache is thread safe
}

impl PkarrPacketLruCache {
    pub fn new(cache_size_mb: Option<u64>) -> Self {
        let cache_size_mb = cache_size_mb.unwrap_or(100); // 100MB by default
        PkarrPacketLruCache {
            cache: Cache::builder()
                .weigher(|_key, value: &CacheItem| -> u32 { value.memory_size() as u32 })
                .max_capacity(cache_size_mb * 1024 * 1024)
                .build(),
        }
    }

    /**
     * Adds a new item to the cache. Makes sure that older items do not override newer items.
     */
    async fn add(&mut self, new_item: CacheItem) -> CacheItem {
        if let Some(mut already_cached) = self.get(&new_item.public_key()).await {
            // Already in cache
            let same_age = new_item.controller_timestamp() == already_cached.controller_timestamp();
            if same_age {
                // Update cached_at timestamp
                already_cached.refresh_updated_at();
                self.cache
                    .insert(already_cached.public_key(), already_cached.clone())
                    .await;
                return already_cached;
            }

            let new_packet_is_older = new_item.controller_timestamp() < already_cached.controller_timestamp();
            if new_packet_is_older {
                // Existing packet is newer than already cached one. Don't update cache. Return existing one.
                return already_cached;
            }
        };

        self.cache.insert(new_item.public_key(), new_item.clone()).await;
        new_item
    }

    /**
     * Adds packet. Makes sure to not override newer instances in the cache.
     */
    pub async fn add_packet(&mut self, packet: SignedPacket) -> CacheItem {
        let new_item = CacheItem::new_packet(packet);
        self.add(new_item).await
    }

    /**
     * Adds not found. Makes sure to not override newer instances in the cache.
     */
    pub async fn add_not_found(&mut self, pubkey: PublicKey) -> CacheItem {
        let new_item = CacheItem::new_not_found(pubkey);
        self.add(new_item).await
    }

    /**
     * Get packet
     */
    pub async fn get(&self, pubkey: &PublicKey) -> Option<CacheItem> {
        let value = self.cache.get(pubkey).await;
        value
    }

    /**
     * Approximated size of the cache in bytes. May not be 100% accurate due to pending counts.
     */
    #[allow(dead_code)]
    pub fn approx_size_bytes(&self) -> u64 {
        self.cache.weighted_size()
    }

    #[allow(dead_code)]
    pub fn entry_count(&self) -> u64 {
        self.cache.entry_count()
    }
}

#[cfg(test)]
mod tests {
    use pkarr::{
        dns::{Name, Packet, ResourceRecord},
        Keypair, SignedPacket, Timestamp,
    };

    use super::*;
    use std::net::Ipv4Addr;

    fn example_signed_packet(keypair: Keypair) -> SignedPacket {
        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.into()),
        );
        packet.answers.push(record);
        let record = ResourceRecord::new(
            Name::new(".").unwrap(),
            pkarr::dns::CLASS::IN,
            100,
            pkarr::dns::rdata::RData::A(ip.into()),
        );
        packet.answers.push(record);
        SignedPacket::new(&keypair, &packet.answers, Timestamp::now()).unwrap()
    }

    #[tokio::test]
    async fn packet_memory_size() {
        let packet = example_signed_packet(Keypair::random());
        let cached = CacheItem::new_packet(packet.clone());
        assert_eq!(cached.memory_size(), 220);
    }

    #[tokio::test]
    async fn cache_size() {
        let mut cache = PkarrPacketLruCache::new(Some(1));
        assert_eq!(cache.approx_size_bytes(), 0);

        for _ in 0..10 {
            cache.add_packet(example_signed_packet(Keypair::random())).await;
        }
        cache.cache.run_pending_tasks().await;
        assert_eq!(cache.approx_size_bytes(), 2200);
    }

    #[tokio::test]
    async fn insert_get() {
        let mut cache = PkarrPacketLruCache::new(Some(1));
        let packet = example_signed_packet(Keypair::random());
        cache.add_packet(packet.clone()).await;

        for _ in 0..10 {
            cache.add_packet(example_signed_packet(Keypair::random())).await;
        }

        let recalled = cache.get(&packet.public_key()).await.expect("Value must be in cache");
        assert_eq!(recalled.public_key(), packet.public_key());
    }

    #[tokio::test]
    async fn override_old_cached_packet() {
        let mut cache = PkarrPacketLruCache::new(Some(1));
        let key = Keypair::random();
        let packet1 = example_signed_packet(key.clone());
        let packet2 = example_signed_packet(key.clone());
        assert_ne!(packet1.timestamp(), packet2.timestamp());

        cache.add_packet(packet1.clone()).await;
        cache.add_packet(packet2.clone()).await;
        let cached = cache.get(&key.public_key()).await.unwrap();
        assert_eq!(packet2.timestamp().as_u64(), cached.controller_timestamp());
    }

    #[tokio::test]
    async fn keep_newer_cached_packet() {
        let mut cache = PkarrPacketLruCache::new(Some(1));
        let key = Keypair::random();
        let packet1 = example_signed_packet(key.clone());
        let packet2 = example_signed_packet(key.clone());
        assert_ne!(packet1.timestamp(), packet2.timestamp());

        cache.add_packet(packet2.clone()).await;
        cache.add_packet(packet1.clone()).await;
        let cached = cache.get(&key.public_key()).await.unwrap();
        assert_eq!(packet2.timestamp().as_u64(), cached.controller_timestamp());
    }

    #[tokio::test]
    async fn override_old_not_found_cached_packet() {
        let mut cache = PkarrPacketLruCache::new(Some(1));
        let key = Keypair::random();
        let packet1 = example_signed_packet(key.clone());
        cache.add(CacheItem::new_not_found(key.public_key())).await;
        let cached = cache.get(&key.public_key()).await.unwrap();
        assert_eq!(cached.controller_timestamp(), 0);
        cache.add_packet(packet1.clone()).await;
        let cached = cache.get(&key.public_key()).await.unwrap();
        assert_eq!(packet1.timestamp().as_u64(), cached.controller_timestamp());
    }

    #[tokio::test]
    async fn not_found_not_overriding_cached_packet() {
        let mut cache = PkarrPacketLruCache::new(Some(1));
        let key = Keypair::random();
        let packet1 = example_signed_packet(key.clone());
        cache.add_packet(packet1.clone()).await;
        cache.add(CacheItem::new_not_found(key.public_key())).await;
        let cached = cache.get(&key.public_key()).await.unwrap();
        assert_eq!(packet1.timestamp().as_u64(), cached.controller_timestamp());
    }
}


================================================
FILE: server/src/resolution/pkd/pkarr_resolver.rs
================================================
use super::{pubkey_parser::parse_pkarr_uri, query_matcher::create_domain_not_found_reply};
use crate::{
    app_context::AppContext,
    config::TopLevelDomain,
    resolution::{dns_packets::ParsedQuery, DnsSocket, DnsSocketError, RateLimiter, RateLimiterBuilder},
};
use pkarr::{
    dns::{Name, Question, ResourceRecord},
    Client,
};
use std::{
    collections::HashMap,
    net::{IpAddr, SocketAddr},
    num::NonZeroU32,
    sync::Arc,
};
use tokio::sync::Mutex;

use super::{
    bootstrap_nodes::MainlineBootstrapResolver,
    pkarr_cache::{CacheItem, PkarrPacketLruCache},
    query_matcher::resolve_query,
};
use pkarr::{
    dns::Packet,
    // mainline::dht::DhtSettings, Error as PkarrError, PkarrClient, PkarrClientAsync,
    PublicKey,
};

/// Errors that a CustomHandler can return.
#[derive(thiserror::Error, Debug)]
pub enum CustomHandlerError {
    /// Lookup failed. Error will be logged. SRVFAIL will be returned to the user.
    #[error(transparent)]
    Failed(#[from] Box<dyn std::error::Error + Send + Sync>),

    /// Handler does not consider itself responsible for this query.
    /// Will fallback to ICANN.
    #[error("Query is not processed by handler. Fallback to ICANN.")]
    Unhandled,

    /// Handler rate limited the IP. Will return RCODE::Refused.
    #[error("Source ip address {0} is rate limited.")]
    RateLimited(IpAddr),
}

#[derive(thiserror::Error, Debug)]
pub enum PkarrResolverError {
    // #[error("Failed to query the DHT with pkarr: {0}")]
    // Dht(#[from] PkarrError),
    #[error("Failed to query the DHT with pkarr: {0}")]
    DnsSocket(#[from] DnsSocketError),
}

/**
 * Pkarr resolver with cache.
 */
#[derive(Clone, Debug)]
pub struct PkarrResolver {
    client: Client,
    cache: PkarrPacketLruCache,
    /**
     * Locks to use to update pkarr packets. This avoids concurrent updates.
     */
    lock_map: Arc<Mutex<HashMap<PublicKey, Arc<Mutex<()>>>>>,
    context: AppContext,
    rate_limiter: Arc<RateLimiter>,
}

impl PkarrResolver {
    /**
     * Resolves the DHT boostrap nodes with the forward server.
     */
    fn resolve_bootstrap_nodes(forward_dns_server: &SocketAddr) -> Vec<SocketAddr> {
        tracing::debug!(
            "Connecting to the DNS forward server {}...",
            forward_dns_server.to_string()
        );

        let addrs = match MainlineBootstrapResolver::get_addrs(forward_dns_server) {
            Ok(addrs) => addrs,
            Err(err) => {
                tracing::error!("{}", err);
                tracing::error!("Connecting to the DNS forward server failed. Couldn't resolve the DHT bootstrap nodes. Is the DNS forward server active?");
                panic!("Resolving bootstrap nodes failed. {}", err);
            }
        };

        tracing::debug!("DHT bootstrap nodes resolved.");
        addrs
    }

    #[cfg(test)]
    pub async fn default() -> Self {
        let context = AppContext::test();
        Self::new(&context).await
    }

    pub async fn new(context: &AppContext) -> Self {
        let addrs = Self::resolve_bootstrap_nodes(&context.config.general.forward);
        let client = Client::builder()
            .minimum_ttl(0)
            .maximum_ttl(0) // Disable Pkarr caching
            .bootstrap(&addrs)
            .build()
            .unwrap();
        let limiter = RateLimiterBuilder::new().max_per_second(context.config.dht.dht_query_rate_limit);
        Self {
            client,
            cache: PkarrPacketLruCache::new(Some(context.config.dht.dht_cache_mb.into())),
            lock_map: Arc::new(Mutex::new(HashMap::new())),
            rate_limiter: Arc::new(limiter.build()),
            context: context.clone(),
        }
    }

    fn is_refresh_needed(&self, item: &CacheItem) -> bool {
        let refresh_needed_in_s =
            item.next_refresh_needed_in_s(self.context.config.dns.min_ttl, self.context.config.dns.max_ttl);
        refresh_needed_in_s == 0
    }

    /**
     * Resolves a public key. Checks the cache first.
     */
    async fn resolve_pubkey_respect_cache(
        &mut self,
        pubkey: &PublicKey,
        from: Option<IpAddr>,
    ) -> Result<CacheItem, CustomHandlerError> {
        if let Some(cached) = self.cache.get(pubkey).await {
            let refresh_needed_in_s =
                cached.next_refresh_needed_in_s(self.context.config.dns.min_ttl, self.context.config.dns.max_ttl);

            if refresh_needed_in_s > 0 {
                tracing::trace!(
                    "Pkarr packet [{pubkey}] found in cache. Cache valid for {}s",
                    refresh_needed_in_s
                );
                return Ok(cached);
            }
        };

        if let Some(ip) = from {
            let is_rate_limited = self.rate_limiter.check_is_limited_and_increase(&ip);
            if is_rate_limited {
                tracing::debug!("{ip} is rate limited from querying the DHT.");
                return Err(CustomHandlerError::RateLimited(ip));
            }
        }

        self.lookup_dht_and_cache(pubkey.clone())
            .await
            .map_err(|err| CustomHandlerError::Failed(err.into()))
    }

    /// Lookup DHT to pull pkarr packet. Will not check the cache first but store any new value in the cache. Returns cached value if lookup fails.
    async fn lookup_dht_and_cache(&mut self, pubkey: PublicKey) -> Result<CacheItem, PkarrResolverError> {
        let mut locked_map = self.lock_map.lock().await;
        let mutex = locked_map
            .entry(pubkey.clone())
            .or_insert_with(|| Arc::new(Mutex::new(())));
        let _guard = mutex.lock().await;

        if let Some(cache) = self.cache.get(&pubkey).await {
            if !self.is_refresh_needed(&cache) {
                // Value got updated in the meantime while aquiring the lock.
                tracing::trace!("Refresh for [{pubkey}] not needed. Value got updated in the meantime.");
                return Ok(cache);
            }
        }

        tracing::trace!("Lookup [{pubkey}] on the DHT.");
        let signed_packet = self.client.resolve(&pubkey).await;
        if signed_packet.is_none() {
            tracing::debug!("DHT lookup for [{pubkey}] failed. Nothing found.");
            return Ok(self.cache.add_not_found(pubkey).await);
        };

        tracing::trace!("Refreshed cache for [{pubkey}].");
        let new_packet = signed_packet.unwrap();
        Ok(self.cache.add_packet(new_packet).await)
    }

    fn remove_tld_if_necessary(&self, mut query: &mut Packet<'_>) -> bool {
        if let Some(tld) = &self.context.config.dht.top_level_domain {
            if tld.question_ends_with_pubkey_tld(query) {
                tld.remove(query);
                return true;
            }
        }
        false
    }

    fn add_tld_if_necessary(&self, mut reply: &mut Packet<'_>) -> bool {
        if let Some(tld) = &self.context.config.dht.top_level_domain {
            tld.add(reply);
            return true;
        }
        false
    }

    /**
     * Resolves a domain with pkarr.
     */
    pub async fn resolve(
        &mut self,
        query: &ParsedQuery,
        from: Option<IpAddr>,
    ) -> std::prelude::v1::Result<Vec<u8>, CustomHandlerError> {
        let mut request = query.packet.parsed().clone();
        let mut removed_tld = self.remove_tld_if_necessary(&mut request);
        if removed_tld {
            tracing::trace!("Removed tld from question: {:?}", request.questions.first().unwrap());
        }

        let question = request
            .questions
            .first()
            .expect("No question in query in pkarr_resolver.")
            .clone();
        let labels = question.qname.get_labels();
        let mut public_key = labels
            .last()
            .expect("Question labels with no domain in pkarr_resolver")
            .to_string();

        let parsed_option = parse_pkarr_uri(&public_key);
        if let Err(e) = parsed_option {
            return match e {
                super::pubkey_parser::PubkeyParserError::InvalidKey(_) => {
                    tracing::trace!("TLD .{public_key} is not a pkarr key. Fallback to ICANN.");
                    Err(CustomHandlerError::Unhandled)
                }
                super::pubkey_parser::PubkeyParserError::ValidButDifferent => {
                    tracing::trace!("TLD .{public_key} is a pkarr key but its last bits are invalid.");
                    Ok(create_domain_not_found_reply(request.id()))
                }
            };
        }

        let pubkey = parsed_option.unwrap();

        match self.resolve_pubkey_respect_cache(&pubkey, from).await {
            Ok(item) => {
                if item.not_found() {
                    return Ok(create_domain_not_found_reply(request.id()));
                };

                let signed_packet = item.unwrap();
                let mut packet = Packet::new_reply(0);
                for rr in signed_packet.all_resource_records() {
                    packet.answers.push(rr.clone());
                }
                let reply = resolve_query(&packet, &request).await;

                let reply = if removed_tld {
                    let mut packet = Packet::parse(&reply).unwrap();
                    self.add_tld_if_necessary(&mut packet);
                    packet.build_bytes_vec().unwrap()
                } else {
                    reply
                };
                Ok(reply)
            }
            Err(err) => Err(err),
        }
    }
}

#[cfg(test)]
mod tests {
    use chrono::{DateTime, Utc};
    use pkarr::{
        dns::{Name, Packet, Question, ResourceRecord},
        Keypair, SignedPacket, Timestamp,
    };

    // use pkarr::dns::{Name, Question, Packet};
    use super::*;
    use std::net::Ipv4Addr;

    trait SignedPacketTimestamp {
        fn chrono_timestamp(&self) -> DateTime<Utc>;
    }

    impl SignedPacketTimestamp for SignedPacket {
        fn chrono_timestamp(&self) -> DateTime<Utc> {
            let timestamp = self.timestamp().as_u64() / 1_000_000;

            DateTime::from_timestamp((timestamp as u32).into(), 0).unwrap()
        }
    }

    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();

        Keypair::from_secret_key(slice)
    }

    async 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.into()),
        );
        packet.answers.push(record);
        let record = ResourceRecord::new(
            Name::new(".").unwrap(),
            pkarr::dns::CLASS::IN,
            100,
            pkarr::dns::rdata::RData::A(ip.into()),
        );
        packet.answers.push(record);
        let signed_packet = SignedPacket::new(&keypair, &packet.answers, Timestamp::now()).unwrap();

        let client = Client::builder().no_relays().build().unwrap();
        let result = client.publish(&signed_packet, None).await;
        result.expect("Should have published.");
    }

    #[tokio::test]
    async fn query_domain() {
        publish_record().await;

        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 query = ParsedQuery::new(query.build_bytes_vec().unwrap()).unwrap();

        let mut resolver = PkarrResolver::default().await;
        let result = resolver.resolve(&query, None).await;
        assert!(result.is_ok());
        let reply_bytes = result.unwrap();
        let reply = Packet::parse(&reply_bytes).unwrap();
        assert_eq!(reply.id(), query.packet.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);
    }

    #[tokio::test]
    async fn query_pubkey() {
        publish_record().await;

        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 query = ParsedQuery::new(query.build_bytes_vec().unwrap()).unwrap();
        let mut resolver = PkarrResolver::default().await;
        let result = resolver.resolve(&query, None).await;
        assert!(result.is_ok());
        let reply_bytes = result.unwrap();
        let reply = Packet::parse(&reply_bytes).unwrap();
        assert_eq!(reply.id(), query.packet.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);
    }

    #[tokio::test]
    async 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 query = ParsedQuery::new(query.build_bytes_vec().unwrap()).unwrap();
        let mut resolver = PkarrResolver::default().await;
        let result = resolver.resolve(&query, None).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn pkarr_invalid_packet1() {
        let pubkey = parse_pkarr_uri("7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy").unwrap();

        let mut resolver = PkarrResolver::default().await;
        let _result = resolver.resolve_pubkey_respect_cache(&pubkey, None).await;
        // assert!(result.is_some());
    }

    #[test]
    fn pkarr_invalid_packet3() {
        let keypair = Keypair::random();
        let pubkey_z32 = keypair.to_z32();

        // Construct reply with single CNAME record.
        let mut packet = Packet::new_reply(0);

        let name = Name::new("www.pknames.p2p").unwrap();
        let data = format!("pknames.p2p.{pubkey_z32}");
        let data = Name::new(&data).unwrap();
        let answer3 = ResourceRecord::new(
            name.clone(),
            pkarr::dns::CLASS::IN,
            100,
            pkarr::dns::rdata::RData::CNAME(pkarr::dns::rdata::CNAME(data)),
        );
        packet.answers.push(answer3);

        // Sign packet
        let signed_packet = SignedPacket::new(&keypair, &packet.answers, Timestamp::now()).unwrap();

        // Serialize and parse again
        let reply_bytes = signed_packet.encoded_packet();
        Packet::parse(&reply_bytes).unwrap(); // Fail
    }
}


================================================
FILE: server/src/resolution/pkd/pubkey_parser.rs
================================================
use pkarr::PublicKey;

#[derive(Debug, thiserror::Error)]
pub enum PubkeyParserError {
    #[error("Invalid public key. {0}")]
    InvalidKey(String),
    #[error("Key is valid zbase32 and length but the last bits are incorrect.")]
    ValidButDifferent,
}

/// Parses a public key domain from it's zbase32 format.
pub fn parse_pkarr_uri(uri: &str) -> Result<PublicKey, PubkeyParserError> {
    let decoded = match zbase32::decode_full_bytes_str(uri) {
        Ok(bytes) => bytes,
        Err(e) => return Err(PubkeyParserError::InvalidKey(e.to_string())),
    };
    if decoded.len() != 32 {
        return Err(PubkeyParserError::InvalidKey(
            "zbase32 pubkey should be 32 bytes but is not.".to_string(),
        ));
    };
    let encoded = zbase32::encode_full_bytes(&decoded);
    if encoded.as_str() != uri {
        tracing::trace!(
            "Uri {uri} is not a valid public key. Error corrected should be {encoded}. Failed to parse pkarr pubkey."
        );
        return Err(PubkeyParserError::ValidButDifferent);
    }

    let trying: Result<PublicKey, _> = uri.try_into();
    trying.map_err(|err| PubkeyParserError::InvalidKey(err.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_pkarr_uri() {
        let uri = "7fmjpcuuzf54hw18bsgi3zihzyh4awseeuq5tmojefaezjbd64cy";
        let pubkey: PublicKey = uri.try_into().unwrap();
        assert_eq!(pubkey.to_string(), uri);
    }
}


================================================
FILE: server/src/resolution/pkd/query_matcher.rs
================================================
use std::{
    net::{Ipv4Addr, Ipv6Addr, SocketAddr},
    time::Duration,
};

use crate::resolution::DnsSocket;
use pkarr::dns::{
    rdata::{self, RData},
    Name, Packet, PacketFlag, Question, ResourceRecord, QTYPE, RCODE, TYPE,
};

/**
 * Handles all possible ways on how to resolve a query into a reply.
 * Does not support forwards, only recursive queries.
 * Max CNAME depth == 1.
 * Uses a query to transforms a pkarr reply into an regular reply
 */
pub async fn resolve_query<'a>(pkarr_packet: &Packet<'a>, query: &Packet<'a>) -> Vec<u8> {
    let question = query.questions.first().unwrap(); // Has at least 1 question based on previous checks.
    let pkarr_reply = resolve_question(pkarr_packet, question).await;
    let pkarr_reply = Packet::parse(&pkarr_reply).unwrap();

    let mut reply = query.clone().into_reply();
    reply.answers = pkarr_reply.answers;
    reply.additional_records = pkarr_reply.additional_records;
    reply.name_servers = pkarr_reply.name_servers;

    reply.build_bytes_vec_compressed().unwrap()
}

/**
 * Resolves a question by filtering the pkarr packet and creating a corresponding reply.
 */
async fn resolve_question<'a>(pkarr_packet: &Packet<'a>, question: &Question<'a>) -> Vec<u8> {
    let mut reply = Packet::new_reply(0);

    let direct_matchs = direct_matches(pkarr_packet, &question.qname, &question.qtype);
    reply.answers.extend(direct_matchs.clone());

    if reply.answers.is_empty() {
        // Not found. Maybe it is a cname?
        let cname_matches = resolve_cname_for(pkarr_packet, question);
        reply.answers.extend(cname_matches);
    };

    if reply.answers.is_empty() {
        // Not found. Maybe we have a name server?
        reply.name_servers = find_nameserver(pkarr_packet, &question.qname);

        // Add all glued A/AAAA records to the additional section
        for ns in reply.name_servers.iter() {
            if let RData::NS(val) = &ns.rdata {
                let name = &val.0;
                let matches_a = direct_matches(pkarr_packet, name, &QTYPE::TYPE(TYPE::A));
                let matches_aaaa = direct_matches(pkarr_packet, name, &QTYPE::TYPE(TYPE::AAAA));
                let merged_matches: Vec<_> = matches_a.into_iter().chain(matches_aaaa.into_iter()).collect();
                reply.additional_records.extend(merged_matches);
            };
        }
    };

    reply.build_bytes_vec_compressed().un
Download .txt
gitextract__l9rdkfn/

├── .github/
│   ├── actions/
│   │   └── setup/
│   │       └── action.yaml
│   └── workflows/
│       ├── build-artifacts.yml
│       └── rust.yaml
├── .gitignore
├── .scripts/
│   ├── build-artifacts.sh
│   └── build_docker.sh
├── Cargo.toml
├── Cross.toml
├── Dockerfile
├── LICENSE
├── README.md
├── cli/
│   ├── Cargo.toml
│   ├── sample/
│   │   ├── README.md
│   │   ├── pkarr.zone
│   │   └── seed.txt
│   └── src/
│       ├── cli.rs
│       ├── commands/
│       │   ├── generate.rs
│       │   ├── mod.rs
│       │   ├── publickey.rs
│       │   ├── publish.rs
│       │   └── resolve.rs
│       ├── external_ip/
│       │   ├── mod.rs
│       │   ├── providers/
│       │   │   ├── external_ip_resolver.rs
│       │   │   ├── icanhazip.rs
│       │   │   ├── identme.rs
│       │   │   ├── ipifyorg.rs
│       │   │   ├── ipinfoio.rs
│       │   │   ├── mod.rs
│       │   │   └── myip.rs
│       │   └── resolver.rs
│       ├── helpers.rs
│       ├── main.rs
│       ├── pkarr_packet.rs
│       └── simple_zone.rs
├── compose.yaml
├── docs/
│   ├── dns-over-https.md
│   ├── dyn-dns.md
│   └── logging.md
├── rustfmt.toml
├── server/
│   ├── Cargo.toml
│   ├── config.sample.toml
│   ├── pkdns.service
│   └── src/
│       ├── app_context.rs
│       ├── config/
│       │   ├── config_file.rs
│       │   ├── data_dir.rs
│       │   ├── mock_data_dir.rs
│       │   ├── mod.rs
│       │   ├── persistent_data_dir.rs
│       │   └── top_level_domain.rs
│       ├── dns_over_https/
│       │   ├── mod.rs
│       │   └── server.rs
│       ├── helpers.rs
│       ├── main.rs
│       └── resolution/
│           ├── dns_packets/
│           │   ├── mod.rs
│           │   ├── parsed_packet.rs
│           │   └── parsed_query.rs
│           ├── dns_socket.rs
│           ├── helpers.rs
│           ├── mod.rs
│           ├── pending_request.rs
│           ├── pkd/
│           │   ├── bootstrap_nodes.rs
│           │   ├── mod.rs
│           │   ├── pkarr_cache.rs
│           │   ├── pkarr_resolver.rs
│           │   ├── pubkey_parser.rs
│           │   └── query_matcher.rs
│           ├── query_id_manager.rs
│           ├── rate_limiter.rs
│           └── response_cache.rs
└── servers.txt
Download .txt
SYMBOL INDEX (369 symbols across 38 files)

FILE: cli/src/cli.rs
  function run_cli (line 6) | pub async fn run_cli() {

FILE: cli/src/commands/generate.rs
  function cli_generate_seed (line 4) | pub async fn cli_generate_seed(_matches: &ArgMatches) {

FILE: cli/src/commands/publickey.rs
  constant SECRET_KEY_LENGTH (line 8) | const SECRET_KEY_LENGTH: usize = 32;
  function read_seed_file (line 10) | fn read_seed_file(matches: &ArgMatches) -> Keypair {
  function parse_seed (line 25) | fn parse_seed(seed: &str) -> Keypair {
  function cli_publickey (line 40) | pub async fn cli_publickey(matches: &ArgMatches) {

FILE: cli/src/commands/publish.rs
  constant SECRET_KEY_LENGTH (line 16) | const SECRET_KEY_LENGTH: usize = 32;
  function fill_dyndns_variables (line 21) | async fn fill_dyndns_variables(zone: &mut String) -> Result<(), anyhow::...
  function read_zone_file (line 43) | async fn read_zone_file(zone_file_path: &str, pubkey: &str) -> SimpleZone {
  function read_seed_file (line 66) | fn read_seed_file(seed_file_path: &str) -> Keypair {
  function parse_seed (line 89) | fn parse_seed(seed: &str) -> anyhow::Result<Keypair> {
  function parse_seed_hex (line 102) | fn parse_seed_hex(seed: &str) -> anyhow::Result<Keypair> {
  function parse_seed_zbase32 (line 111) | fn parse_seed_zbase32(seed: &str) -> anyhow::Result<Keypair> {
  function cli_publish (line 120) | pub async fn cli_publish(matches: &ArgMatches) {

FILE: cli/src/commands/resolve.rs
  function resolve_pkarr (line 10) | async fn resolve_pkarr(uri: &str) -> (PkarrPacket, DateTime<Utc>) {
  function get_arg_pubkey (line 26) | fn get_arg_pubkey(matches: &ArgMatches) -> Option<PublicKey> {
  function cli_resolve (line 32) | pub async fn cli_resolve(matches: &ArgMatches) {

FILE: cli/src/external_ip/providers/external_ip_resolver.rs
  type ExternalIpResult (line 9) | type ExternalIpResult<T> = Result<T, ExternalIpResolverError>;
  type IpFuture (line 11) | type IpFuture<T> = Pin<Box<dyn Future<Output = ExternalIpResult<T>>>>;
  type IpResolver (line 13) | type IpResolver<T> = Pin<Box<dyn Fn() -> IpFuture<T>>>;
  type ProviderResolver (line 15) | pub struct ProviderResolver {
    method new (line 22) | pub fn new(name: String, ipv4: IpResolver<Ipv4Addr>, ipv6: IpResolver<...
    method ipv4 (line 27) | pub async fn ipv4(&self) -> Result<Ipv4Addr, ExternalIpResolverError> {
    method ipv6 (line 33) | pub async fn ipv6(&self) -> Result<Ipv6Addr, ExternalIpResolverError> {
  type ExternalIpResolverError (line 40) | pub enum ExternalIpResolverError {
  function resolve_ipv4_with_url (line 49) | pub async fn resolve_ipv4_with_url<T: IntoUrl>(url: T) -> Result<Ipv4Add...
  function resolve_ipv6_with_url (line 58) | pub async fn resolve_ipv6_with_url<T: IntoUrl>(url: T) -> Result<Ipv6Add...

FILE: cli/src/external_ip/providers/icanhazip.rs
  function resolve_ipv4 (line 7) | pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
  function resolve_ipv6 (line 11) | pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
  function get_resolver (line 15) | pub fn get_resolver() -> ProviderResolver {
  function test_ipv4 (line 28) | async fn test_ipv4() {
  function test_ipv6 (line 35) | async fn test_ipv6() {

FILE: cli/src/external_ip/providers/identme.rs
  function resolve_ipv4 (line 7) | pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
  function resolve_ipv6 (line 11) | pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
  function get_resolver (line 15) | pub fn get_resolver() -> ProviderResolver {
  function test_ipv4 (line 28) | async fn test_ipv4() {
  function test_ipv6 (line 35) | async fn test_ipv6() {

FILE: cli/src/external_ip/providers/ipifyorg.rs
  function resolve_ipv4 (line 7) | pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
  function resolve_ipv6 (line 11) | pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
  function get_resolver (line 15) | pub fn get_resolver() -> ProviderResolver {
  function test_ipv4 (line 28) | async fn test_ipv4() {
  function test_ipv6 (line 35) | async fn test_ipv6() {

FILE: cli/src/external_ip/providers/ipinfoio.rs
  type IpResponse (line 8) | struct IpResponse {
  function resolve_ipv4 (line 12) | pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
  function resolve_ipv6 (line 19) | pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
  function get_resolver (line 26) | pub fn get_resolver() -> ProviderResolver {
  function test_ipv4 (line 39) | async fn test_ipv4() {
  function test_ipv6 (line 46) | async fn test_ipv6() {

FILE: cli/src/external_ip/providers/myip.rs
  type IpResponse (line 8) | struct IpResponse {
  function resolve_ipv4 (line 12) | pub async fn resolve_ipv4() -> Result<Ipv4Addr, ExternalIpResolverError> {
  function resolve_ipv6 (line 19) | pub async fn resolve_ipv6() -> Result<Ipv6Addr, ExternalIpResolverError> {
  function get_resolver (line 26) | pub fn get_resolver() -> ProviderResolver {
  function test_ipv4 (line 39) | async fn test_ipv4() {
  function test_ipv6 (line 46) | async fn test_ipv6() {

FILE: cli/src/external_ip/resolver.rs
  function resolve_ipv4 (line 10) | pub async fn resolve_ipv4() -> Result<(Ipv4Addr, String), &'static str> {
  function resolve_ipv6 (line 36) | pub async fn resolve_ipv6() -> Result<(Ipv6Addr, String), &'static str> {
  function test_ipv4 (line 65) | async fn test_ipv4() {
  function test_ipv6 (line 72) | async fn test_ipv6() {

FILE: cli/src/helpers.rs
  function construct_pkarr_client (line 4) | pub fn construct_pkarr_client() -> Client {
  function nts_to_chrono (line 10) | pub fn nts_to_chrono(ntc: Timestamp) -> chrono::DateTime<chrono::Utc> {

FILE: cli/src/main.rs
  function main (line 12) | async fn main() -> Result<(), anyhow::Error> {

FILE: cli/src/pkarr_packet.rs
  type PkarrPacket (line 11) | pub struct PkarrPacket {
    method empty (line 16) | pub fn empty() -> Self {
    method by_data (line 22) | pub fn by_data(data: Vec<u8>) -> Self {
    method parsed (line 26) | pub fn parsed(&self) -> Packet {
    method to_records (line 30) | pub fn to_records(&self) -> Vec<PkarrRecord> {
    method answers_len (line 38) | pub fn answers_len(&self) -> usize {
    method is_emtpy (line 42) | pub fn is_emtpy(&self) -> bool {
    method fmt (line 48) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type PkarrRecord (line 66) | pub struct PkarrRecord {
    method by_data (line 72) | pub fn by_data(data: Vec<u8>) -> Result<Self, anyhow::Error> {
    method by_resource_record (line 85) | pub fn by_resource_record(rr: &ResourceRecord) -> Self {
    method get_resource_record (line 94) | pub fn get_resource_record(&self) -> ResourceRecord {
    method pubkey (line 99) | pub fn pubkey(&self) -> String {
    method name (line 105) | pub fn name(&self) -> String {
    method ttl (line 117) | pub fn ttl(&self) -> u32 {
    method data_as_strings (line 122) | pub fn data_as_strings(&self) -> (&str, String) {
    method fmt (line 166) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

FILE: cli/src/simple_zone.rs
  type SimpleZone (line 15) | pub struct SimpleZone {
    method read (line 23) | pub fn read(simplified_zone: String, pubkey: &str) -> Result<Self, any...
    method generate_soa (line 35) | fn generate_soa(pubkey: &str) -> String {
    method parse_simplified_zone (line 53) | fn parse_simplified_zone(simplified_zone: String, pubkey: &str) -> Res...
    method entries_to_simple_dns_packet (line 79) | fn entries_to_simple_dns_packet(entries: Vec<Entry>) -> Result<Vec<u8>...
  function simplified_zone (line 183) | fn simplified_zone() -> String {
  function test_create_entries (line 206) | fn test_create_entries() {
  function test_transform (line 216) | fn test_transform() {
  function test_pkarr_records (line 225) | fn test_pkarr_records() {
  function test_read_zone_txt1 (line 242) | fn test_read_zone_txt1() {
  function test_read_zone_txt2 (line 262) | fn test_read_zone_txt2() {
  function test_read_zone_txt3 (line 279) | fn test_read_zone_txt3() {

FILE: server/src/app_context.rs
  type AppContext (line 4) | pub struct AppContext {
    method from_data_dir (line 9) | pub fn from_data_dir(data_dir: impl DataDir) -> Result<Self, anyhow::E...
    method test (line 16) | pub fn test() -> Self {
  function test_from_data_dir (line 30) | fn test_from_data_dir() {

FILE: server/src/config/config_file.rs
  type ConfigReadError (line 8) | pub enum ConfigReadError {
  constant SAMPLE_CONFIG (line 18) | pub const SAMPLE_CONFIG: &str = include_str!("../../config.sample.toml");
  type ConfigToml (line 21) | pub struct ConfigToml {
    method sample (line 32) | pub fn sample() -> String {
    method commented_out_sample (line 38) | pub fn commented_out_sample() -> String {
    method from_file (line 61) | pub fn from_file(path: impl AsRef<Path>) -> Result<Self, ConfigReadErr...
    method test (line 68) | pub fn test() -> Self {
    type Error (line 77) | type Error = ConfigReadError;
    method try_from (line 78) | fn try_from(value: &str) -> Result<Self, Self::Error> {
  type General (line 85) | pub struct General {
  method default (line 100) | fn default() -> Self {
  function default_socket (line 110) | fn default_socket() -> SocketAddr {
  function default_forward (line 114) | fn default_forward() -> SocketAddr {
  function default_false (line 118) | fn default_false() -> bool {
  function default_none (line 122) | fn default_none() -> Option<SocketAddr> {
  type Dns (line 127) | pub struct Dns {
  method default (line 151) | fn default() -> Self {
  function default_min_ttl (line 164) | fn default_min_ttl() -> u64 {
  function default_max_ttl (line 168) | fn default_max_ttl() -> u64 {
  function default_query_rate_limit (line 172) | fn default_query_rate_limit() -> u32 {
  function default_query_rate_limit_burst (line 176) | fn default_query_rate_limit_burst() -> u32 {
  function default_icann_cache_mb (line 180) | fn default_icann_cache_mb() -> u64 {
  function default_max_recursion_depth (line 184) | fn default_max_recursion_depth() -> u8 {
  type Dht (line 189) | pub struct Dht {
  function deserialize_top_level_domain (line 203) | fn deserialize_top_level_domain<'de, D>(deserializer: D) -> Result<Optio...
  function default_cache_mb (line 216) | fn default_cache_mb() -> NonZeroU64 {
  function default_dht_rate_limit (line 220) | fn default_dht_rate_limit() -> u32 {
  function default_dht_rate_limit_burst (line 224) | fn default_dht_rate_limit_burst() -> u32 {
  function default_top_level_domain (line 228) | fn default_top_level_domain() -> Option<TopLevelDomain> {
  method default (line 233) | fn default() -> Self {
  function test_parse_sample_config (line 248) | fn test_parse_sample_config() {
  function test_commented_out_sample (line 253) | fn test_commented_out_sample() {
  function test_default_config_top_level_domain (line 259) | fn test_default_config_top_level_domain() {

FILE: server/src/config/data_dir.rs
  type DataDir (line 10) | pub trait DataDir: std::fmt::Debug + DynClone + Send + Sync {
    method path (line 12) | fn path(&self) -> &Path;
    method ensure_data_dir_exists_and_is_writable (line 15) | fn ensure_data_dir_exists_and_is_writable(&self) -> anyhow::Result<()>;
    method read_or_create_config_file (line 19) | fn read_or_create_config_file(&self) -> anyhow::Result<ConfigToml>;

FILE: server/src/config/mock_data_dir.rs
  type MockDataDir (line 11) | pub struct MockDataDir {
    method new (line 21) | pub fn new(config_toml: super::ConfigToml) -> anyhow::Result<Self> {
    method test (line 29) | pub fn test() -> Self {
  method default (line 36) | fn default() -> Self {
  method path (line 42) | fn path(&self) -> &Path {
  method ensure_data_dir_exists_and_is_writable (line 46) | fn ensure_data_dir_exists_and_is_writable(&self) -> anyhow::Result<()> {
  method read_or_create_config_file (line 50) | fn read_or_create_config_file(&self) -> anyhow::Result<super::ConfigToml> {

FILE: server/src/config/persistent_data_dir.rs
  type PersistentDataDir (line 12) | pub struct PersistentDataDir {
    method new (line 19) | pub fn new(path: PathBuf) -> Self {
    method expand_home_dir (line 27) | fn expand_home_dir(path: PathBuf) -> PathBuf {
    method get_config_file_path (line 47) | pub fn get_config_file_path(&self) -> PathBuf {
    method write_sample_config_file (line 51) | fn write_sample_config_file(&self) -> anyhow::Result<()> {
  method default (line 61) | fn default() -> Self {
  method path (line 68) | fn path(&self) -> &Path {
  method ensure_data_dir_exists_and_is_writable (line 74) | fn ensure_data_dir_exists_and_is_writable(&self) -> anyhow::Result<()> {
  method read_or_create_config_file (line 88) | fn read_or_create_config_file(&self) -> anyhow::Result<ConfigToml> {
  function test_expand_home_dir (line 107) | pub fn test_expand_home_dir() {
  function test_ensure_data_dir_exists_and_is_accessible (line 116) | pub fn test_ensure_data_dir_exists_and_is_accessible() {
  function test_get_default_config_file_path_exists (line 129) | pub fn test_get_default_config_file_path_exists() {
  function test_read_or_create_config_file (line 144) | pub fn test_read_or_create_config_file() {
  function test_read_or_create_config_file_dont_override_existing_file (line 157) | pub fn test_read_or_create_config_file_dont_override_existing_file() {

FILE: server/src/config/top_level_domain.rs
  type TopLevelDomain (line 6) | pub struct TopLevelDomain(pub String);
    method new (line 9) | pub fn new(tld: String) -> Self {
    method label (line 13) | pub fn label(&self) -> &str {
    method question_ends_with_pubkey_tld (line 18) | pub fn question_ends_with_pubkey_tld(&self, packet: &Packet<'_>) -> bo...
    method remove (line 29) | pub fn remove(&self, packet: &mut Packet<'_>) {
    method name_ends_with_pubkey_tld (line 64) | pub fn name_ends_with_pubkey_tld(&self, name: &Name<'_>) -> bool {
    method name_ends_with_pubkey (line 83) | pub fn name_ends_with_pubkey(&self, name: &Name<'_>) -> bool {
    method add (line 97) | pub fn add(&self, reply: &mut Packet<'_>) {
  function create_query_with_domain (line 137) | fn create_query_with_domain(domain: &str) -> Vec<u8> {
  function create_reply_with_domain (line 150) | fn create_reply_with_domain(domain: &str) -> Vec<u8> {
  function is_pkarr_with_tld_valid_2_label (line 165) | async fn is_pkarr_with_tld_valid_2_label() {
  function is_pkarr_with_tld_valid_3_label (line 173) | async fn is_pkarr_with_tld_valid_3_label() {
  function is_pkarr_with_tld_fail_1_label (line 181) | async fn is_pkarr_with_tld_fail_1_label() {
  function is_pkarr_with_tld_fail_2_label_no_pubkey (line 189) | async fn is_pkarr_with_tld_fail_2_label_no_pubkey() {
  function is_pkarr_with_tld_fail_2_label_wrong_tld (line 197) | async fn is_pkarr_with_tld_fail_2_label_wrong_tld() {
  function remove_tld_success_2_labels (line 205) | async fn remove_tld_success_2_labels() {
  function remove_tld_success_3_labels (line 218) | async fn remove_tld_success_3_labels() {
  function add_success_1_label (line 234) | async fn add_success_1_label() {
  function add_success_2_label (line 259) | async fn add_success_2_label() {

FILE: server/src/dns_over_https/server.rs
  constant ERROR_PREFIX (line 26) | const ERROR_PREFIX: &str = "
  function validate_accept_header (line 40) | fn validate_accept_header(headers: &HeaderMap) -> Result<(), (StatusCode...
  function decode_dns_base64_packet (line 61) | fn decode_dns_base64_packet(param: &String) -> Result<Vec<u8>, (StatusCo...
  function get_lowest_ttl (line 83) | fn get_lowest_ttl(reply: &[u8]) -> u32 {
  function extract_client_ip (line 103) | fn extract_client_ip(request_addr: &SocketAddr, headers: &HeaderMap) -> ...
  function query_to_response (line 118) | async fn query_to_response(query: Vec<u8>, dns_socket: &mut DnsSocket, c...
  function dns_query_get (line 133) | async fn dns_query_get(
  function dns_query_post (line 152) | async fn dns_query_post(
  type AppState (line 170) | pub struct AppState {
  function create_app (line 174) | fn create_app(dns_socket: DnsSocket) -> Router {
  function run_doh_server (line 187) | pub async fn run_doh_server(addr: SocketAddr, dns_socket: DnsSocket) -> ...
  function query_doh_wireformat_get (line 210) | async fn query_doh_wireformat_get() {
  function query_doh_wireformat_post (line 248) | async fn query_doh_wireformat_post() {
  function wrong_content_type (line 287) | async fn wrong_content_type() {

FILE: server/src/helpers.rs
  function set_full_stacktrace_as_default (line 9) | pub(crate) fn set_full_stacktrace_as_default() {
  function enable_logging (line 19) | pub(crate) fn enable_logging(verbose: bool) {
  function wait_on_ctrl_c (line 61) | pub(crate) async fn wait_on_ctrl_c() {

FILE: server/src/main.rs
  type Cli (line 20) | struct Cli {
  function main (line 35) | async fn main() -> Result<(), Box<dyn Error>> {

FILE: server/src/resolution/dns_packets/parsed_packet.rs
  method try_from_bytes (line 22) | pub fn try_from_bytes(bytes: Vec<u8>) -> Result<Self, pkarr::dns::Simple...
  method packet (line 27) | pub fn packet(&self) -> &Packet {
  method raw_bytes (line 32) | pub fn raw_bytes(&self) -> &Vec<u8> {
  method clone (line 38) | fn clone(&self) -> Self {
  function from (line 45) | fn from(val: Inner) -> Self {
  type ParsedPacket (line 53) | pub struct ParsedPacket {
    method new (line 58) | pub fn new(raw_bytes: Vec<u8>) -> Result<Self, pkarr::dns::SimpleDnsEr...
    method id (line 63) | pub fn id(&self) -> u16 {
    method parsed (line 68) | pub fn parsed(&self) -> &Packet {
    method raw_bytes (line 73) | pub fn raw_bytes(&self) -> &Vec<u8> {
    method is_reply (line 78) | pub fn is_reply(&self) -> bool {
    method is_query (line 83) | pub fn is_query(&self) -> bool {
    method create_refused_reply (line 88) | pub fn create_refused_reply(&self) -> Vec<u8> {
    method create_server_fail_reply (line 95) | pub fn create_server_fail_reply(&self) -> Vec<u8> {
  function from (line 103) | fn from(val: ParsedPacket) -> Self {
  function new (line 114) | async fn new() {

FILE: server/src/resolution/dns_packets/parsed_query.rs
  type ParseQueryError (line 8) | pub enum ParseQueryError {
  type ParsedQuery (line 17) | pub struct ParsedQuery {
    method new (line 23) | pub fn new(bytes: Vec<u8>) -> Result<Self, ParseQueryError> {
    method validate (line 31) | fn validate(&self) -> Result<(), anyhow::Error> {
    method question (line 48) | pub fn question(&self) -> &Question {
    method is_any_type (line 53) | pub fn is_any_type(&self) -> bool {
    method is_recursion_desired (line 57) | pub fn is_recursion_desired(&self) -> bool {
    type Error (line 79) | type Error = ParseQueryError;
    method try_from (line 80) | fn try_from(value: ParsedPacket) -> Result<Self, Self::Error> {
  method fmt (line 63) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  method from (line 88) | fn from(val: ParsedQuery) -> Self {
  function new (line 100) | async fn new() {
  function tryfrom_parsed_packet (line 114) | async fn tryfrom_parsed_packet() {

FILE: server/src/resolution/dns_socket.rs
  type DnsSocketError (line 45) | pub enum DnsSocketError {
  type DnsSocket (line 63) | pub struct DnsSocket {
    method default_random_socket (line 78) | pub async fn default_random_socket() -> tokio::io::Result<Self> {
    method new (line 90) | pub async fn new(context: &AppContext) -> tokio::io::Result<Self> {
    method is_recursion_available (line 114) | fn is_recursion_available(&self) -> bool {
    method send_to (line 119) | pub async fn send_to(&self, buffer: &[u8], target: &SocketAddr) -> tok...
    method start_receive_loop (line 125) | pub fn start_receive_loop(&self) -> oneshot::Sender<()> {
    method receive_datagram (line 147) | async fn receive_datagram(&mut self) -> Result<(), DnsSocketError> {
    method query_me_recursively_raw (line 198) | pub async fn query_me_recursively_raw(&mut self, query: Vec<u8>, from:...
    method query_me_recursively_with_log (line 214) | pub async fn query_me_recursively_with_log(&mut self, query: &ParsedQu...
    method query_me_recursively (line 222) | async fn query_me_recursively(&mut self, query: &ParsedQuery, from: Op...
    method query_me_once (line 403) | async fn query_me_once(
    method forward (line 449) | pub async fn forward(
    method forward_to_icann (line 481) | pub async fn forward_to_icann(
    method extract_query_id (line 504) | fn extract_query_id(&self, query: &[u8]) -> Result<u16, SimpleDnsError> {
    method create_refused_reply (line 509) | fn create_refused_reply(query_id: u16) -> Vec<u8> {
    method create_server_fail_reply (line 516) | fn create_server_fail_reply(query_id: u16) -> Vec<u8> {
  function publish_domain (line 558) | async fn publish_domain() {
  function resolve_query_recursively (line 649) | async fn resolve_query_recursively(query: Vec<u8>) -> Vec<u8> {
  function recursion_cname_icann (line 659) | async fn recursion_cname_icann() {
  function recursion_cname_icann_with_tld (line 681) | async fn recursion_cname_icann_with_tld() {
  function recursion_cname_pkd (line 703) | async fn recursion_cname_pkd() {
  function recursion_cname_pkd2 (line 726) | async fn recursion_cname_pkd2() {
  function recursion_cname_infinite (line 751) | async fn recursion_cname_infinite() {
  function recursion_not_found1 (line 771) | async fn recursion_not_found1() {
  function recursion_not_found2 (line 791) | async fn recursion_not_found2() {
  function recursion_ns_pkd (line 808) | async fn recursion_ns_pkd() {
  function recursion_ns_soa_icann (line 835) | async fn recursion_ns_soa_icann() {

FILE: server/src/resolution/helpers.rs
  function replace_packet_id (line 4) | pub fn replace_packet_id(packet: &[u8], new_id: u16) -> Result<Vec<u8>, ...

FILE: server/src/resolution/pending_request.rs
  type PendingRequest (line 14) | pub struct PendingRequest {
  type PendingRequestKey (line 28) | struct PendingRequestKey {
  type PendingRequestStore (line 41) | pub struct PendingRequestStore {
    method insert (line 47) | pub fn insert(&mut self, request: PendingRequest) {
    method remove_by_forward_id (line 57) | pub fn remove_by_forward_id(&mut self, forward_query_id: &u16, from: &...
    method new (line 66) | pub fn new() -> Self {

FILE: server/src/resolution/pkd/bootstrap_nodes.rs
  type DomainPortAddr (line 10) | pub(crate) struct DomainPortAddr {
    method new (line 16) | pub const fn new(domain: &'static str, port: u16) -> Self {
    method fmt (line 22) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type MainlineBootstrapResolverError (line 35) | pub enum MainlineBootstrapResolverError {
  type MainlineBootstrapResolver (line 46) | pub(crate) struct MainlineBootstrapResolver {
    method new (line 51) | pub fn new(dns_server: SocketAddr) -> Result<Self, MainlineBootstrapRe...
    method lookup_domain (line 59) | fn lookup_domain(&self, domain: &str) -> Result<Option<IpAddr>, Mainli...
    method lookup (line 88) | fn lookup(&self, boostrap_node: &DomainPortAddr) -> Result<Option<Sock...
    method get_bootstrap_nodes (line 94) | pub fn get_bootstrap_nodes(&self) -> Result<Vec<SocketAddr>, MainlineB...
    method get_addrs (line 117) | pub fn get_addrs(dns_server: &SocketAddr) -> Result<Vec<SocketAddr>, M...
  function query_domain (line 129) | async fn query_domain() {
  function query_bootstrap_node (line 136) | async fn query_bootstrap_node() {
  function query_bootstrap_nodes (line 145) | async fn query_bootstrap_nodes() {

FILE: server/src/resolution/pkd/pkarr_cache.rs
  function get_timestamp_seconds (line 13) | fn get_timestamp_seconds() -> u64 {
  type CacheItem (line 24) | pub enum CacheItem {
    method new_packet (line 42) | pub fn new_packet(packet: SignedPacket) -> Self {
    method new_not_found (line 49) | pub fn new_not_found(pubkey: PublicKey) -> Self {
    method is_found (line 57) | pub fn is_found(&self) -> bool {
    method not_found (line 67) | pub fn not_found(&self) -> bool {
    method is_packet (line 72) | pub fn is_packet(&self) -> bool {
    method unwrap (line 85) | pub fn unwrap(self) -> SignedPacket {
    method public_key (line 97) | pub fn public_key(&self) -> PublicKey {
    method refresh_updated_at (line 113) | pub fn refresh_updated_at(&mut self) {
    method controller_timestamp (line 134) | pub fn controller_timestamp(&self) -> u64 {
    method last_updated_at (line 147) | fn last_updated_at(&self) -> u64 {
    method lowest_answer_ttl (line 164) | fn lowest_answer_ttl(&self) -> Option<u64> {
    method memory_size (line 180) | pub fn memory_size(&self) -> usize {
    method next_refresh_needed_in_s (line 198) | pub fn next_refresh_needed_in_s(&self, min_ttl: u64, max_ttl: u64) -> ...
  type PkarrPacketLruCache (line 219) | pub struct PkarrPacketLruCache {
    method new (line 224) | pub fn new(cache_size_mb: Option<u64>) -> Self {
    method add (line 237) | async fn add(&mut self, new_item: CacheItem) -> CacheItem {
    method add_packet (line 264) | pub async fn add_packet(&mut self, packet: SignedPacket) -> CacheItem {
    method add_not_found (line 272) | pub async fn add_not_found(&mut self, pubkey: PublicKey) -> CacheItem {
    method get (line 280) | pub async fn get(&self, pubkey: &PublicKey) -> Option<CacheItem> {
    method approx_size_bytes (line 289) | pub fn approx_size_bytes(&self) -> u64 {
    method entry_count (line 294) | pub fn entry_count(&self) -> u64 {
  function example_signed_packet (line 309) | fn example_signed_packet(keypair: Keypair) -> SignedPacket {
  function packet_memory_size (line 330) | async fn packet_memory_size() {
  function cache_size (line 337) | async fn cache_size() {
  function insert_get (line 349) | async fn insert_get() {
  function override_old_cached_packet (line 363) | async fn override_old_cached_packet() {
  function keep_newer_cached_packet (line 377) | async fn keep_newer_cached_packet() {
  function override_old_not_found_cached_packet (line 391) | async fn override_old_not_found_cached_packet() {
  function not_found_not_overriding_cached_packet (line 404) | async fn not_found_not_overriding_cached_packet() {

FILE: server/src/resolution/pkd/pkarr_resolver.rs
  type CustomHandlerError (line 32) | pub enum CustomHandlerError {
  type PkarrResolverError (line 48) | pub enum PkarrResolverError {
  type PkarrResolver (line 59) | pub struct PkarrResolver {
    method resolve_bootstrap_nodes (line 74) | fn resolve_bootstrap_nodes(forward_dns_server: &SocketAddr) -> Vec<Soc...
    method default (line 94) | pub async fn default() -> Self {
    method new (line 99) | pub async fn new(context: &AppContext) -> Self {
    method is_refresh_needed (line 117) | fn is_refresh_needed(&self, item: &CacheItem) -> bool {
    method resolve_pubkey_respect_cache (line 126) | async fn resolve_pubkey_respect_cache(
    method lookup_dht_and_cache (line 158) | async fn lookup_dht_and_cache(&mut self, pubkey: PublicKey) -> Result<...
    method remove_tld_if_necessary (line 185) | fn remove_tld_if_necessary(&self, mut query: &mut Packet<'_>) -> bool {
    method add_tld_if_necessary (line 195) | fn add_tld_if_necessary(&self, mut reply: &mut Packet<'_>) -> bool {
    method resolve (line 206) | pub async fn resolve(
  type SignedPacketTimestamp (line 283) | trait SignedPacketTimestamp {
    method chrono_timestamp (line 284) | fn chrono_timestamp(&self) -> DateTime<Utc>;
    method chrono_timestamp (line 288) | fn chrono_timestamp(&self) -> DateTime<Utc> {
  function get_test_keypair (line 295) | fn get_test_keypair() -> Keypair {
  function publish_record (line 304) | async fn publish_record() {
  function query_domain (line 333) | async fn query_domain() {
  function query_pubkey (line 362) | async fn query_pubkey() {
  function query_invalid_pubkey (line 390) | async fn query_invalid_pubkey() {
  function pkarr_invalid_packet1 (line 408) | async fn pkarr_invalid_packet1() {
  function pkarr_invalid_packet3 (line 417) | fn pkarr_invalid_packet3() {

FILE: server/src/resolution/pkd/pubkey_parser.rs
  type PubkeyParserError (line 4) | pub enum PubkeyParserError {
  function parse_pkarr_uri (line 12) | pub fn parse_pkarr_uri(uri: &str) -> Result<PublicKey, PubkeyParserError> {
  function test_parse_pkarr_uri (line 39) | fn test_parse_pkarr_uri() {

FILE: server/src/resolution/pkd/query_matcher.rs
  function resolve_query (line 18) | pub async fn resolve_query<'a>(pkarr_packet: &Packet<'a>, query: &Packet...
  function resolve_question (line 34) | async fn resolve_question<'a>(pkarr_packet: &Packet<'a>, question: &Ques...
  function resolve_cname_for (line 68) | fn resolve_cname_for<'a>(pkarr_packet: &Packet<'a>, question: &Question<...
  function direct_matches (line 94) | fn direct_matches<'a>(pkarr_packet: &Packet<'a>, qname: &Name<'a>, qtype...
  function find_nameserver (line 107) | fn find_nameserver<'a>(pkarr_packet: &Packet<'a>, qname: &Name<'a>) -> V...
  function create_domain_not_found_reply (line 196) | pub fn create_domain_not_found_reply(query_id: u16) -> Vec<u8> {
  function example_pkarr_reply (line 216) | fn example_pkarr_reply() -> (Vec<u8>, PublicKey) {
  function simple_a_question (line 265) | async fn simple_a_question() {
  function a_question_with_cname (line 291) | async fn a_question_with_cname() {
  function a_question_with_ns (line 324) | async fn a_question_with_ns() {
  function a_question_with_ns_subdomain (line 352) | async fn a_question_with_ns_subdomain() {
  function simple_a_query (line 381) | async fn simple_a_query() {

FILE: server/src/resolution/query_id_manager.rs
  type QueryIdManager (line 15) | pub struct QueryIdManager {
    method get_next (line 23) | pub fn get_next(&mut self, server: &SocketAddr) -> u16 {
    method new (line 34) | pub fn new() -> Self {

FILE: server/src/resolution/rate_limiter.rs
  type RateLimitingKey (line 16) | enum RateLimitingKey {
    method from_ipv4 (line 25) | pub fn from_ipv4(ip: Ipv4Addr) -> Self {
    method from_ipv6 (line 32) | pub fn from_ipv6(ip: Ipv6Addr) -> Self {
    method from (line 43) | fn from(value: IpAddr) -> Self {
  method hash (line 52) | fn hash<H: Hasher>(&self, state: &mut H) {
  type RateLimiterBuilder (line 66) | pub struct RateLimiterBuilder {
    method new (line 73) | pub fn new() -> Self {
    method max_per_second (line 85) | pub fn max_per_second(mut self, limit: u32) -> Self {
    method max_per_minute (line 94) | pub fn max_per_minute(mut self, limit: u32) -> Self {
    method burst_size (line 101) | pub fn burst_size(mut self, size: u32) -> Self {
    method build (line 107) | pub fn build(self) -> RateLimiter {
  type RateLimiter (line 132) | pub struct RateLimiter {
    method check_is_limited_and_increase (line 140) | pub fn check_is_limited_and_increase(&self, ip: &IpAddr) -> bool {

FILE: server/src/resolution/response_cache.rs
  type CacheItem (line 9) | pub struct CacheItem {
    method new (line 16) | pub fn new(query: Vec<u8>, response: Vec<u8>) -> Result<Self, anyhow::...
    method derive_query_key (line 28) | pub fn derive_query_key(query: &[u8]) -> Result<String, anyhow::Error> {
    method response_packet (line 37) | fn response_packet(&self) -> Packet {
    method lowest_answer_ttl (line 43) | pub fn lowest_answer_ttl(&self) -> Option<u64> {
    method memory_size (line 53) | pub fn memory_size(&self) -> usize {
    method expires_in (line 58) | pub fn expires_in(&self, min_ttl: u64, max_ttl: u64) -> SystemTime {
    method is_outdated (line 68) | pub fn is_outdated(&self, min_ttl: u64, max_ttl: u64) -> bool {
  type IcannLruCache (line 77) | pub struct IcannLruCache {
    method new (line 84) | pub fn new(cache_size_mb: u64, min_ttl: u64, max_ttl: u64) -> Self {
    method add (line 96) | pub async fn add(&mut self, query: Vec<u8>, response: Vec<u8>) -> Resu...
    method get (line 103) | pub async fn get(&self, query: &[u8]) -> Result<Option<CacheItem>, any...
    method approx_size_bytes (line 117) | pub fn approx_size_bytes(&self) -> u64 {
    method entry_count (line 122) | pub fn entry_count(&self) -> u64 {
  function example_query_response (line 132) | fn example_query_response(ttl: u32) -> (Vec<u8>, Vec<u8>) {
  function add_and_get (line 158) | async fn add_and_get() {
  function outdated_get (line 169) | async fn outdated_get() {
  function zero_cache_size (line 179) | async fn zero_cache_size() {
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (226K chars).
[
  {
    "path": ".github/actions/setup/action.yaml",
    "chars": 627,
    "preview": "name: Shared Setup\n\ndescription: \"Install Rust, checkout code and install dependencies.\"\n\n\nruns:\n  using: \"composite\"\n  "
  },
  {
    "path": ".github/workflows/build-artifacts.yml",
    "chars": 1509,
    "preview": "# This workflow builds the artifacts for the release.\n# It creates the gzips and uploads them as artifacts.\n# The artifa"
  },
  {
    "path": ".github/workflows/rust.yaml",
    "chars": 1195,
    "preview": "name: Pipeline\n\non:\n  pull_request:\n    branches:\n      - \"*\"\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest"
  },
  {
    "path": ".gitignore",
    "chars": 272,
    "preview": "# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# These are backup files generated by ru"
  },
  {
    "path": ".scripts/build-artifacts.sh",
    "chars": 3068,
    "preview": "#!/bin/bash\n\n# -------------------------------------------------------------------------------------------------\n# This "
  },
  {
    "path": ".scripts/build_docker.sh",
    "chars": 796,
    "preview": "#!/bin/bash\n\nVERSION=$(cargo pkgid -p pkdns | cut -d@ -f2)\ndocker build --platform linux/amd64 -t synonymsoft/pkdns:$VER"
  },
  {
    "path": "Cargo.toml",
    "chars": 142,
    "preview": "[workspace]\nmembers = [\n  \"server\", \n  \"cli\"\n]\n\n\n\n# See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949"
  },
  {
    "path": "Cross.toml",
    "chars": 775,
    "preview": "# --------------------------------------------------------------------------------\n# Costum defined Apple cross-compile "
  },
  {
    "path": "Dockerfile",
    "chars": 922,
    "preview": "# ========================\n# Build Image\n# ========================\nFROM rust:1.86.0-alpine3.20 AS builder\n\n# Install bu"
  },
  {
    "path": "LICENSE",
    "chars": 1089,
    "preview": "The MIT License (MIT)\n\nCopyright (Severin Alexander Bühler) 2023 \n\nPermission is hereby granted, free of charge, to any "
  },
  {
    "path": "README.md",
    "chars": 6289,
    "preview": "# pkdns\n\n[![GitHub Release](https://img.shields.io/github/v/release/pubky/pkdns)](https://github.com/pubky/pkdns/release"
  },
  {
    "path": "cli/Cargo.toml",
    "chars": 787,
    "preview": "[package]\nname = \"pkdns-cli\"\nversion = \"0.7.1\"\nauthors = [\"SeverinAlexB <severin@synonym.to>\"]\nedition = \"2021\"\n\n# See m"
  },
  {
    "path": "cli/sample/README.md",
    "chars": 1325,
    "preview": "## pkdns-cli sample\n\nThis is an example on how to announce your own records on the mainline DHT.\n\n- `seed.txt` contains "
  },
  {
    "path": "cli/sample/pkarr.zone",
    "chars": 146,
    "preview": "$TTL 60\n\n@      IN    A      127.0.0.1\ndynv4  IN    A      {external_ipv4}\ndynv6  IN    AAAA   {external_ipv6}\n\ntext   I"
  },
  {
    "path": "cli/sample/seed.txt",
    "chars": 65,
    "preview": "292bbfb6a5bc1ec3f6d01a7c8e3fac1782d364ca41a1847497e9e3bc949dbb9a\n"
  },
  {
    "path": "cli/src/cli.rs",
    "chars": 2141,
    "preview": "use crate::commands::{cli_publickey, generate::cli_generate_seed, publish::cli_publish, resolve::cli_resolve};\n\n/**\n * M"
  },
  {
    "path": "cli/src/commands/generate.rs",
    "chars": 218,
    "preview": "use clap::ArgMatches;\nuse pkarr::Keypair;\n\npub async fn cli_generate_seed(_matches: &ArgMatches) {\n    let keypair = Key"
  },
  {
    "path": "cli/src/commands/mod.rs",
    "chars": 102,
    "preview": "pub mod generate;\nmod publickey;\npub mod publish;\npub mod resolve;\n\npub use publickey::cli_publickey;\n"
  },
  {
    "path": "cli/src/commands/publickey.rs",
    "chars": 1286,
    "preview": "use clap::ArgMatches;\nuse pkarr::Keypair;\nuse std::{\n    fs::read_to_string,\n    path::{Path, PathBuf},\n};\n\nconst SECRET"
  },
  {
    "path": "cli/src/commands/publish.rs",
    "chars": 4971,
    "preview": "use std::io::Write;\nuse std::{\n    fs::read_to_string,\n    path::{Path, PathBuf},\n};\n\nuse anyhow::anyhow;\n\nuse clap::Arg"
  },
  {
    "path": "cli/src/commands/resolve.rs",
    "chars": 1515,
    "preview": "use chrono::{DateTime, Utc};\nuse clap::ArgMatches;\nuse pkarr::PublicKey;\n\nuse crate::{\n    helpers::{construct_pkarr_cli"
  },
  {
    "path": "cli/src/external_ip/mod.rs",
    "chars": 78,
    "preview": "mod providers;\nmod resolver;\n\npub use resolver::{resolve_ipv4, resolve_ipv6};\n"
  },
  {
    "path": "cli/src/external_ip/providers/external_ip_resolver.rs",
    "chars": 1803,
    "preview": "use std::{\n    future::Future,\n    net::{AddrParseError, Ipv4Addr, Ipv6Addr},\n    pin::Pin,\n};\n\nuse reqwest::IntoUrl;\n\nt"
  },
  {
    "path": "cli/src/external_ip/providers/icanhazip.rs",
    "chars": 1018,
    "preview": "use std::net::{Ipv4Addr, Ipv6Addr};\n\nuse super::external_ip_resolver::{\n    resolve_ipv4_with_url, resolve_ipv6_with_url"
  },
  {
    "path": "cli/src/external_ip/providers/identme.rs",
    "chars": 999,
    "preview": "use std::net::{Ipv4Addr, Ipv6Addr};\n\nuse super::external_ip_resolver::{\n    resolve_ipv4_with_url, resolve_ipv6_with_url"
  },
  {
    "path": "cli/src/external_ip/providers/ipifyorg.rs",
    "chars": 1005,
    "preview": "use std::net::{Ipv4Addr, Ipv6Addr};\n\nuse super::external_ip_resolver::{\n    resolve_ipv4_with_url, resolve_ipv6_with_url"
  },
  {
    "path": "cli/src/external_ip/providers/ipinfoio.rs",
    "chars": 1270,
    "preview": "use std::net::{Ipv4Addr, Ipv6Addr};\n\nuse serde::Deserialize;\n\nuse super::external_ip_resolver::{ExternalIpResolverError,"
  },
  {
    "path": "cli/src/external_ip/providers/mod.rs",
    "chars": 161,
    "preview": "pub mod icanhazip;\npub mod identme;\npub mod ipifyorg;\npub mod ipinfoio;\npub mod myip;\n\nmod external_ip_resolver;\npub use"
  },
  {
    "path": "cli/src/external_ip/providers/myip.rs",
    "chars": 1267,
    "preview": "use std::net::{Ipv4Addr, Ipv6Addr};\n\nuse serde::Deserialize;\n\nuse super::external_ip_resolver::{ExternalIpResolverError,"
  },
  {
    "path": "cli/src/external_ip/resolver.rs",
    "chars": 2254,
    "preview": "use rand::seq::SliceRandom;\nuse rand::thread_rng;\nuse std::net::{Ipv4Addr, Ipv6Addr};\n\nuse super::providers::ProviderRes"
  },
  {
    "path": "cli/src/helpers.rs",
    "chars": 448,
    "preview": "use pkarr::{Client, Timestamp};\n\n/// Construct new pkarr client with no cache, no resolver, only DHT\npub fn construct_pk"
  },
  {
    "path": "cli/src/main.rs",
    "chars": 205,
    "preview": "use cli::run_cli;\n\nmod cli;\n\nmod commands;\nmod external_ip;\nmod helpers;\nmod pkarr_packet;\nmod simple_zone;\n\n#[tokio::ma"
  },
  {
    "path": "cli/src/pkarr_packet.rs",
    "chars": 4769,
    "preview": "use core::fmt;\nuse std::net::{Ipv4Addr, Ipv6Addr};\n\nuse anyhow::anyhow;\nuse pkarr::dns::{rdata::RData, Name, Packet, Res"
  },
  {
    "path": "cli/src/simple_zone.rs",
    "chars": 11188,
    "preview": "use anyhow::anyhow;\nuse domain::{\n    base::Rtype,\n    zonefile::inplace::{Entry, Zonefile},\n};\nuse pkarr::dns::{Name, P"
  },
  {
    "path": "compose.yaml",
    "chars": 427,
    "preview": "services:\n  pkdns:\n    image: \"synonymsoft/pkdns:latest\"\n    container_name: pkdns\n    ports: \n      - \"53:53/udp\"\n    r"
  },
  {
    "path": "docs/dns-over-https.md",
    "chars": 1686,
    "preview": "# DNS over HTTPS (DoH)\n\n## What is DoH?\n\nDNS over HTTPS (DoH) encrypts DNS queries by sending them over HTTPS instead of"
  },
  {
    "path": "docs/dyn-dns.md",
    "chars": 1306,
    "preview": "# DynDNS\n\nDynDNS (Dynamic Domain Name System) is a service that allows users to assign a fixed domain name to a device w"
  },
  {
    "path": "docs/logging.md",
    "chars": 643,
    "preview": "# Logging\n\nBy default pkdns stays silent. Use `--verbose` to make pkdns log all queries.\n\n## Advanced\n\nThe log output ca"
  },
  {
    "path": "rustfmt.toml",
    "chars": 15,
    "preview": "max_width = 120"
  },
  {
    "path": "server/Cargo.toml",
    "chars": 1131,
    "preview": "[package]\nname = \"pkdns\"\nversion = \"0.7.1\"\nauthors = [\"SeverinAlexB <severin@synonym.to>\"]\nedition = \"2021\"\n\n# See more "
  },
  {
    "path": "server/config.sample.toml",
    "chars": 1681,
    "preview": "# PKDNS configuration file\n# More information on https://github.com/pubky/pkdns/server/sample-config.toml\n\n[general]\n# D"
  },
  {
    "path": "server/pkdns.service",
    "chars": 411,
    "preview": "# https://github.com/pubky/pkdns/blob/master/server/pkdns.service\n[Unit]\nDescription=pkdns - Self-Sovereign And Censorsh"
  },
  {
    "path": "server/src/app_context.rs",
    "chars": 743,
    "preview": "use crate::config::{ConfigToml, DataDir};\n\n#[derive(Debug, Clone, Default)]\npub struct AppContext {\n    pub config: Conf"
  },
  {
    "path": "server/src/config/config_file.rs",
    "chars": 7037,
    "preview": "use serde::{Deserialize, Deserializer, Serialize};\nuse std::{fs, net::SocketAddr, num::NonZeroU64, path::Path};\n\nuse cra"
  },
  {
    "path": "server/src/config/data_dir.rs",
    "chars": 769,
    "preview": "use dyn_clone::DynClone;\nuse std::path::Path;\n\nuse crate::config::ConfigToml;\n\n/// A trait for the data directory.\n/// U"
  },
  {
    "path": "server/src/config/mock_data_dir.rs",
    "chars": 1464,
    "preview": "use std::path::Path;\n\nuse super::DataDir;\n\n/// Mock data directory for testing.\n///\n/// It uses a temporary directory to"
  },
  {
    "path": "server/src/config/mod.rs",
    "chars": 310,
    "preview": "mod config_file;\nmod data_dir;\n#[cfg(test)]\nmod mock_data_dir;\nmod persistent_data_dir;\nmod top_level_domain;\n\npub use c"
  },
  {
    "path": "server/src/config/persistent_data_dir.rs",
    "chars": 6603,
    "preview": "use super::{data_dir::DataDir, ConfigToml};\nuse std::{\n    io::Write,\n    path::{Path, PathBuf},\n};\n\n/// The data direct"
  },
  {
    "path": "server/src/config/top_level_domain.rs",
    "chars": 11218,
    "preview": "use pkarr::dns::{Name, Packet, Question, ResourceRecord};\n\n/// Top Level Domain like .pkd with the capability\n/// to rem"
  },
  {
    "path": "server/src/dns_over_https/mod.rs",
    "chars": 45,
    "preview": "mod server;\n\npub use server::run_doh_server;\n"
  },
  {
    "path": "server/src/dns_over_https/server.rs",
    "chars": 10447,
    "preview": "//! RFC8484 Dns-over-http wireformat\n//! https://datatracker.ietf.org/doc/html/rfc8484\n//! The implementation works but "
  },
  {
    "path": "server/src/helpers.rs",
    "chars": 1887,
    "preview": "use std::env;\nuse tracing::Level;\nuse tracing_subscriber::{filter::Targets, layer::SubscriberExt, util::SubscriberInitEx"
  },
  {
    "path": "server/src/main.rs",
    "chars": 2803,
    "preview": "use clap::Parser;\nuse dns_over_https::run_doh_server;\nuse helpers::{enable_logging, set_full_stacktrace_as_default, wait"
  },
  {
    "path": "server/src/resolution/dns_packets/mod.rs",
    "chars": 129,
    "preview": "mod parsed_packet;\nmod parsed_query;\n\npub use parsed_packet::ParsedPacket;\npub use parsed_query::{ParseQueryError, Parse"
  },
  {
    "path": "server/src/resolution/dns_packets/parsed_packet.rs",
    "chars": 3375,
    "preview": "use anyhow::anyhow;\nuse chrono::format::Parsed;\nuse pkarr::dns::{Packet, PacketFlag};\nuse self_cell::self_cell;\nuse std:"
  },
  {
    "path": "server/src/resolution/dns_packets/parsed_query.rs",
    "chars": 3940,
    "preview": "use std::fmt::Display;\n\nuse super::ParsedPacket;\nuse anyhow::anyhow;\nuse pkarr::dns::{Packet, PacketFlag, Question, QTYP"
  },
  {
    "path": "server/src/resolution/dns_socket.rs",
    "chars": 36127,
    "preview": "#![allow(unused)]\nuse crate::{\n    app_context::AppContext,\n    resolution::{helpers::replace_packet_id, pkd::CustomHand"
  },
  {
    "path": "server/src/resolution/helpers.rs",
    "chars": 441,
    "preview": "use pkarr::dns::{Packet, SimpleDnsError};\n\n/// Replaces the id of a dns packet.\npub fn replace_packet_id(packet: &[u8], "
  },
  {
    "path": "server/src/resolution/mod.rs",
    "chars": 467,
    "preview": "#![allow(unused)]\n\n/**\n * Basic module to process DNS queries with a UDP socket.\n * Allows to hook into the socket and p"
  },
  {
    "path": "server/src/resolution/pending_request.rs",
    "chars": 2192,
    "preview": "#![allow(unused)]\n\nuse std::{\n    collections::HashMap,\n    net::SocketAddr,\n    sync::{Arc, Mutex},\n    time::Instant,\n"
  },
  {
    "path": "server/src/resolution/pkd/bootstrap_nodes.rs",
    "chars": 5460,
    "preview": "use std::{\n    net::{IpAddr, SocketAddr, UdpSocket},\n    time::Duration,\n};\n\nuse anyhow::anyhow;\nuse rustdns::{Class, Ex"
  },
  {
    "path": "server/src/resolution/pkd/mod.rs",
    "chars": 214,
    "preview": "mod bootstrap_nodes;\nmod pkarr_cache;\nmod pkarr_resolver;\nmod pubkey_parser;\nmod query_matcher;\n\npub use pkarr_resolver:"
  },
  {
    "path": "server/src/resolution/pkd/pkarr_cache.rs",
    "chars": 12794,
    "preview": "//!\n//! Goal1: Cache things as long as possible to make any attack on the DHT unfeasible.\n//! Goal2: Prevent attackers f"
  },
  {
    "path": "server/src/resolution/pkd/pkarr_resolver.rs",
    "chars": 15781,
    "preview": "use super::{pubkey_parser::parse_pkarr_uri, query_matcher::create_domain_not_found_reply};\nuse crate::{\n    app_context:"
  },
  {
    "path": "server/src/resolution/pkd/pubkey_parser.rs",
    "chars": 1446,
    "preview": "use pkarr::PublicKey;\n\n#[derive(Debug, thiserror::Error)]\npub enum PubkeyParserError {\n    #[error(\"Invalid public key. "
  },
  {
    "path": "server/src/resolution/pkd/query_matcher.rs",
    "chars": 14204,
    "preview": "use std::{\n    net::{Ipv4Addr, Ipv6Addr, SocketAddr},\n    time::Duration,\n};\n\nuse crate::resolution::DnsSocket;\nuse pkar"
  },
  {
    "path": "server/src/resolution/query_id_manager.rs",
    "chars": 893,
    "preview": "#![allow(unused)]\n\nuse std::{\n    collections::HashMap,\n    net::SocketAddr,\n    sync::{Arc, Mutex},\n};\n\n/**\n * Thread-s"
  },
  {
    "path": "server/src/resolution/rate_limiter.rs",
    "chars": 4472,
    "preview": "use std::{\n    hash::{Hash, Hasher},\n    net::{IpAddr, Ipv4Addr, Ipv6Addr},\n    num::NonZeroU32,\n    sync::Arc,\n};\n\nuse "
  },
  {
    "path": "server/src/resolution/response_cache.rs",
    "chars": 6156,
    "preview": "use std::time::{Duration, SystemTime, UNIX_EPOCH};\n\nuse anyhow::anyhow;\nuse moka::{future::Cache, policy::EvictionPolicy"
  },
  {
    "path": "servers.txt",
    "chars": 117,
    "preview": "# A list of free to use pkdns servers.\n\n34.65.109.99\n66.78.40.76\n\n\n# DNS-over-HTTPS\nhttps://pkdns.pubky.org/dns-query"
  }
]

About this extraction

This page contains the full source code of the pubky/pkdns GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 70 files (209.5 KB), approximately 55.4k tokens, and a symbol index with 369 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!