Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20bf14e91c | ||
|
|
e860731c01 | ||
|
|
f556b60ce4 | ||
|
|
422726f1c8 | ||
|
|
dd021d8642 | ||
|
|
f20c72a829 | ||
|
|
44cd17cf84 | ||
|
|
fb0a21e5e6 | ||
|
|
66b937f710 | ||
|
|
524aed7fa1 | ||
|
|
11e3fdeae6 | ||
|
|
636c45b3d7 | ||
|
|
f602687d93 | ||
|
|
b8b0fda1e0 | ||
|
|
9a3fae9a0c | ||
|
|
a31ac36957 | ||
|
|
9001b14fed | ||
|
|
63ac69a222 |
34
.github/dependabot.yml
vendored
Normal file
34
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns: ["*"]
|
||||
update-types: ["minor", "patch"]
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns: ["*"]
|
||||
update-types: ["minor", "patch"]
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns: ["*"]
|
||||
update-types: ["minor", "patch"]
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
check-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: clippy
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
check-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: build
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
- name: test
|
||||
run: cargo test
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: numa-windows-x86_64
|
||||
path: target/debug/numa.exe
|
||||
|
||||
19
.github/workflows/homebrew-bump.yml
vendored
19
.github/workflows/homebrew-bump.yml
vendored
@@ -1,8 +1,12 @@
|
||||
name: Bump Homebrew Tap
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to bump (e.g. 0.10.0 or v0.10.0)'
|
||||
type: string
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
@@ -16,17 +20,14 @@ jobs:
|
||||
bump:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Determine version
|
||||
id: ver
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "release" ]; then
|
||||
V="${{ github.event.release.tag_name }}"
|
||||
else
|
||||
V="${{ github.event.inputs.version }}"
|
||||
fi
|
||||
V="${V#v}"
|
||||
V="${INPUT_VERSION#v}"
|
||||
echo "version=$V" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fetch sha256 checksums from release assets
|
||||
|
||||
23
.github/workflows/release.yml
vendored
23
.github/workflows/release.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
(Get-FileHash "${{ matrix.name }}.zip" -Algorithm SHA256).Hash.ToLower() + " ${{ matrix.name }}.zip" | Out-File "${{ matrix.name }}.zip.sha256" -Encoding ascii
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: |
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -96,23 +96,22 @@ jobs:
|
||||
needs: [build, publish]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
# Use a PAT (not the default GITHUB_TOKEN) so the resulting
|
||||
# `release: published` event propagates to downstream workflows
|
||||
# like homebrew-bump.yml. Events triggered by GITHUB_TOKEN are
|
||||
# deliberately not propagated by GitHub Actions to prevent
|
||||
# infinite loops; PAT-authored events are the documented escape
|
||||
# hatch. Reusing HOMEBREW_TAP_GITHUB_TOKEN (already a PAT used
|
||||
# by homebrew-bump.yml itself) keeps the secret surface flat.
|
||||
token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
*.tar.gz
|
||||
*.zip
|
||||
*.sha256
|
||||
|
||||
bump-homebrew:
|
||||
needs: release
|
||||
uses: ./.github/workflows/homebrew-bump.yml
|
||||
with:
|
||||
version: ${{ github.ref_name }}
|
||||
secrets: inherit
|
||||
|
||||
8
.github/workflows/static.yml
vendored
8
.github/workflows/static.yml
vendored
@@ -30,18 +30,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Install pandoc
|
||||
run: sudo apt-get install -y pandoc
|
||||
- name: Generate blog HTML
|
||||
run: make blog
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
uses: actions/configure-pages@v6
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: './site'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@v5
|
||||
|
||||
241
Cargo.lock
generated
241
Cargo.lock
generated
@@ -17,6 +17,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloca"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
@@ -75,18 +84,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.9.0"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6"
|
||||
checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
version = "0.6.2"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
|
||||
checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
|
||||
dependencies = [
|
||||
"asn1-rs-derive",
|
||||
"asn1-rs-impl",
|
||||
@@ -94,15 +103,15 @@ dependencies = [
|
||||
"nom",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-derive"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
|
||||
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -368,25 +377,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.5.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
||||
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
"page_size",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
@@ -394,9 +402,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
@@ -441,9 +449,9 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "9.0.0"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
|
||||
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"displaydoc",
|
||||
@@ -702,12 +710,6 @@ version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
@@ -755,9 +757,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
|
||||
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -770,7 +772,6 @@ dependencies = [
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
@@ -810,7 +811,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -944,17 +945,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
@@ -963,9 +953,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -1143,7 +1133,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "numa"
|
||||
version = "0.10.2"
|
||||
version = "0.10.3"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"axum",
|
||||
@@ -1162,7 +1152,7 @@ dependencies = [
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socket2 0.5.10",
|
||||
"socket2",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
@@ -1172,9 +1162,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.7.1"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9"
|
||||
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
]
|
||||
@@ -1197,6 +1187,16 @@ version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.6"
|
||||
@@ -1219,12 +1219,6 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
@@ -1314,8 +1308,8 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.6.3",
|
||||
"thiserror 2.0.18",
|
||||
"socket2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -1336,7 +1330,7 @@ dependencies = [
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -1351,7 +1345,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
@@ -1422,9 +1416,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.13.2"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
|
||||
checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"ring",
|
||||
@@ -1655,11 +1649,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1698,16 +1692,6 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.3"
|
||||
@@ -1761,33 +1745,13 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1869,24 +1833,24 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.50.0"
|
||||
version = "1.51.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
||||
checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.3",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.1"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
|
||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1918,44 +1882,42 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
@@ -2188,6 +2150,22 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
@@ -2197,6 +2175,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
@@ -2361,12 +2345,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
@@ -2382,9 +2363,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.16.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
|
||||
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"data-encoding",
|
||||
@@ -2394,7 +2375,7 @@ dependencies = [
|
||||
"oid-registry",
|
||||
"ring",
|
||||
"rusticata-macros",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "numa"
|
||||
version = "0.10.2"
|
||||
version = "0.10.3"
|
||||
authors = ["razvandimescu <razvan@dimescu.com>"]
|
||||
edition = "2021"
|
||||
description = "Portable DNS resolver in Rust — .numa local domains, ad blocking, developer overrides, DNS-over-HTTPS"
|
||||
@@ -14,7 +14,7 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "time",
|
||||
axum = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
toml = "0.8"
|
||||
toml = "1.1"
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
reqwest = { version = "0.12", features = ["rustls-tls", "gzip", "http2"], default-features = false }
|
||||
@@ -22,8 +22,8 @@ hyper = { version = "1", features = ["client", "http1", "server"] }
|
||||
hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"] }
|
||||
http-body-util = "0.1"
|
||||
futures = "0.3"
|
||||
socket2 = { version = "0.5", features = ["all"] }
|
||||
rcgen = { version = "0.13", features = ["pem", "x509-parser"] }
|
||||
socket2 = { version = "0.6", features = ["all"] }
|
||||
rcgen = { version = "0.14", features = ["pem", "x509-parser"] }
|
||||
time = "0.3"
|
||||
rustls = "0.23"
|
||||
tokio-rustls = "0.26"
|
||||
@@ -32,7 +32,7 @@ ring = "0.17"
|
||||
rustls-pemfile = "2.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
tower = { version = "0.5", features = ["util"] }
|
||||
http = "1"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM rust:1.88-alpine AS builder
|
||||
FROM rust:1.94-alpine AS builder
|
||||
RUN apk add --no-cache musl-dev cmake make perl
|
||||
WORKDIR /app
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
@@ -11,7 +11,7 @@ COPY numa.toml com.numa.dns.plist numa.service ./
|
||||
RUN touch src/main.rs src/lib.rs
|
||||
RUN cargo build --release
|
||||
|
||||
FROM alpine:3.20
|
||||
FROM alpine:3.23
|
||||
COPY --from=builder /app/target/release/numa /usr/local/bin/numa
|
||||
EXPOSE 53/udp 80/tcp 443/tcp 853/tcp 5380/tcp
|
||||
ENTRYPOINT ["numa"]
|
||||
|
||||
239
src/buffer.rs
239
src/buffer.rs
@@ -84,6 +84,11 @@ impl BytePacketBuffer {
|
||||
|
||||
/// Read a qname, handling label compression (pointer jumps).
|
||||
/// Converts wire format like [3]www[6]google[3]com[0] into "www.google.com".
|
||||
///
|
||||
/// Label bytes are escaped per RFC 1035 §5.1:
|
||||
/// - literal `.` within a label → `\.`
|
||||
/// - literal `\` → `\\`
|
||||
/// - bytes outside `0x21..=0x7E` (excluding `.` and `\`) → `\DDD` (3-digit decimal)
|
||||
pub fn read_qname(&mut self, outstr: &mut String) -> Result<()> {
|
||||
let mut pos = self.pos();
|
||||
let mut jumped = false;
|
||||
@@ -121,7 +126,18 @@ impl BytePacketBuffer {
|
||||
|
||||
let str_buffer = self.get_range(pos, len as usize)?;
|
||||
for &b in str_buffer {
|
||||
outstr.push(b.to_ascii_lowercase() as char);
|
||||
let c = b.to_ascii_lowercase();
|
||||
match c {
|
||||
b'.' => outstr.push_str("\\."),
|
||||
b'\\' => outstr.push_str("\\\\"),
|
||||
0x21..=0x7E => outstr.push(c as char),
|
||||
_ => {
|
||||
outstr.push('\\');
|
||||
outstr.push((b'0' + c / 100) as char);
|
||||
outstr.push((b'0' + (c / 10) % 10) as char);
|
||||
outstr.push((b'0' + c % 10) as char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delim = ".";
|
||||
@@ -163,24 +179,68 @@ impl BytePacketBuffer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a qname in wire format, parsing RFC 1035 §5.1 text escapes.
|
||||
/// See `read_qname` for the escape grammar.
|
||||
pub fn write_qname(&mut self, qname: &str) -> Result<()> {
|
||||
if qname.is_empty() || qname == "." {
|
||||
self.write_u8(0)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for label in qname.split('.') {
|
||||
let len = label.len();
|
||||
if len == 0 {
|
||||
continue; // skip empty labels from trailing dot
|
||||
}
|
||||
if len > 0x3f {
|
||||
return Err("Single label exceeds 63 characters of length".into());
|
||||
let bytes = qname.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
let len_pos = self.pos;
|
||||
self.write_u8(0)?; // placeholder length byte, backpatched below
|
||||
let body_start = self.pos;
|
||||
|
||||
while i < bytes.len() && bytes[i] != b'.' {
|
||||
let b = bytes[i];
|
||||
if b == b'\\' {
|
||||
i += 1;
|
||||
let c1 = *bytes.get(i).ok_or("trailing backslash in qname")?;
|
||||
if c1.is_ascii_digit() {
|
||||
let c2 = *bytes
|
||||
.get(i + 1)
|
||||
.ok_or("invalid \\DDD escape: expected 3 digits")?;
|
||||
let c3 = *bytes
|
||||
.get(i + 2)
|
||||
.ok_or("invalid \\DDD escape: expected 3 digits")?;
|
||||
if !c2.is_ascii_digit() || !c3.is_ascii_digit() {
|
||||
return Err("invalid \\DDD escape: expected 3 digits".into());
|
||||
}
|
||||
let val =
|
||||
(c1 - b'0') as u16 * 100 + (c2 - b'0') as u16 * 10 + (c3 - b'0') as u16;
|
||||
if val > 255 {
|
||||
return Err(format!("\\DDD escape out of range: {}", val).into());
|
||||
}
|
||||
self.write_u8(val as u8)?;
|
||||
i += 3;
|
||||
} else {
|
||||
// \. \\ and any other \X → literal next byte
|
||||
self.write_u8(c1)?;
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
self.write_u8(b)?;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if self.pos - body_start > 0x3f {
|
||||
return Err("Single label exceeds 63 characters of length".into());
|
||||
}
|
||||
}
|
||||
|
||||
self.write_u8(len as u8)?;
|
||||
for b in label.as_bytes() {
|
||||
self.write_u8(*b)?;
|
||||
let label_len = self.pos - body_start;
|
||||
if label_len == 0 && i < bytes.len() {
|
||||
// Empty label from leading/consecutive dots — roll back the placeholder.
|
||||
self.pos = len_pos;
|
||||
} else {
|
||||
self.set(len_pos, label_len as u8)?;
|
||||
}
|
||||
|
||||
if i < bytes.len() && bytes[i] == b'.' {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,3 +272,160 @@ impl BytePacketBuffer {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn roundtrip(wire: &[u8]) -> String {
|
||||
let mut buf = BytePacketBuffer::from_bytes(wire);
|
||||
let mut out = String::new();
|
||||
buf.read_qname(&mut out).unwrap();
|
||||
out
|
||||
}
|
||||
|
||||
fn write_then_read(text: &str) -> String {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname(text).unwrap();
|
||||
let wire_end = buf.pos();
|
||||
buf.seek(0).unwrap();
|
||||
let mut out = String::new();
|
||||
buf.read_qname(&mut out).unwrap();
|
||||
assert_eq!(
|
||||
buf.pos(),
|
||||
wire_end,
|
||||
"reader should consume exactly what writer wrote"
|
||||
);
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_plain_domain() {
|
||||
// [3]www[6]google[3]com[0]
|
||||
let wire = b"\x03www\x06google\x03com\x00";
|
||||
assert_eq!(roundtrip(wire), "www.google.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_label_with_literal_dot_is_escaped() {
|
||||
// fanf2's example: [8]exa.mple[3]com[0] — two labels, first contains 0x2E
|
||||
let wire = b"\x08exa.mple\x03com\x00";
|
||||
assert_eq!(roundtrip(wire), "exa\\.mple.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_label_with_backslash_is_escaped() {
|
||||
// [4]a\bc[3]com[0]
|
||||
let wire = b"\x04a\\bc\x03com\x00";
|
||||
assert_eq!(roundtrip(wire), "a\\\\bc.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_label_with_nonprintable_byte_uses_decimal_escape() {
|
||||
// [4]\x00foo[3]com[0] — null byte at label start
|
||||
let wire = b"\x04\x00foo\x03com\x00";
|
||||
assert_eq!(roundtrip(wire), "\\000foo.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_label_with_space_uses_decimal_escape() {
|
||||
// Space (0x20) is outside 0x21..=0x7E, so it must be decimal-escaped.
|
||||
let wire = b"\x05a b c\x00";
|
||||
assert_eq!(roundtrip(wire), "a\\032b\\032c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_plain_domain() {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname("www.google.com").unwrap();
|
||||
assert_eq!(&buf.buf[..buf.pos], b"\x03www\x06google\x03com\x00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_escaped_dot_does_not_split_label() {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname("exa\\.mple.com").unwrap();
|
||||
assert_eq!(&buf.buf[..buf.pos], b"\x08exa.mple\x03com\x00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_escaped_backslash() {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname("a\\\\bc.com").unwrap();
|
||||
assert_eq!(&buf.buf[..buf.pos], b"\x04a\\bc\x03com\x00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_decimal_escape_yields_raw_byte() {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname("\\000foo.com").unwrap();
|
||||
assert_eq!(&buf.buf[..buf.pos], b"\x04\x00foo\x03com\x00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_skips_empty_labels() {
|
||||
// Leading dot — first (empty) label is rolled back.
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname(".foo.com").unwrap();
|
||||
assert_eq!(&buf.buf[..buf.pos], b"\x03foo\x03com\x00");
|
||||
|
||||
// Consecutive dots — middle empty label is rolled back.
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname("foo..com").unwrap();
|
||||
assert_eq!(&buf.buf[..buf.pos], b"\x03foo\x03com\x00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_rejects_out_of_range_decimal_escape() {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
assert!(buf.write_qname("\\999foo.com").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_rejects_trailing_backslash() {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
assert!(buf.write_qname("foo\\").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_rejects_short_decimal_escape() {
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
assert!(buf.write_qname("\\1").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_rejects_label_over_63_bytes() {
|
||||
// 64 bytes exceeds the wire-format label cap.
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
assert!(buf.write_qname(&"a".repeat(64)).is_err());
|
||||
|
||||
// 63 bytes is the maximum permitted label length.
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
assert!(buf.write_qname(&"a".repeat(63)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_preserves_dot_in_label() {
|
||||
assert_eq!(write_then_read("exa\\.mple.com"), "exa\\.mple.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_preserves_backslash_in_label() {
|
||||
assert_eq!(write_then_read("a\\\\b.com"), "a\\\\b.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_preserves_nonprintable_byte() {
|
||||
assert_eq!(write_then_read("\\000foo.com"), "\\000foo.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_name_empty_and_dot_both_produce_single_zero() {
|
||||
let mut a = BytePacketBuffer::new();
|
||||
a.write_qname("").unwrap();
|
||||
let mut b = BytePacketBuffer::new();
|
||||
b.write_qname(".").unwrap();
|
||||
assert_eq!(&a.buf[..a.pos], b"\x00");
|
||||
assert_eq!(&b.buf[..b.pos], b"\x00");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use log::{debug, trace};
|
||||
use ring::digest;
|
||||
use ring::signature;
|
||||
|
||||
use crate::buffer::BytePacketBuffer;
|
||||
use crate::cache::{DnsCache, DnssecStatus};
|
||||
use crate::packet::DnsPacket;
|
||||
use crate::question::QueryType;
|
||||
@@ -720,22 +721,29 @@ pub fn verify_ds(ds: &DnsRecord, dnskey: &DnsRecord, owner: &str) -> bool {
|
||||
|
||||
// -- Canonical wire format --
|
||||
|
||||
/// Encode a DNS name in canonical wire form per RFC 4034 §6.2:
|
||||
/// uncompressed, with ASCII letters lowercased.
|
||||
///
|
||||
/// Lowercasing happens *after* escape resolution because `\065` yields
|
||||
/// `'A'`, which canonical form must convert to `'a'`.
|
||||
pub fn name_to_wire(name: &str) -> Vec<u8> {
|
||||
let mut wire = Vec::with_capacity(name.len() + 2);
|
||||
if name == "." || name.is_empty() {
|
||||
wire.push(0);
|
||||
return wire;
|
||||
}
|
||||
for label in name.split('.') {
|
||||
if label.is_empty() {
|
||||
continue;
|
||||
}
|
||||
wire.push(label.len() as u8);
|
||||
for &b in label.as_bytes() {
|
||||
wire.push(b.to_ascii_lowercase());
|
||||
let mut buf = BytePacketBuffer::new();
|
||||
buf.write_qname(name)
|
||||
.expect("name_to_wire: input must parse as a valid DNS name");
|
||||
let mut wire = buf.filled().to_vec();
|
||||
|
||||
let mut i = 0;
|
||||
while i < wire.len() {
|
||||
let label_len = wire[i] as usize;
|
||||
if label_len == 0 {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
let end = i + label_len;
|
||||
wire[i..end].make_ascii_lowercase();
|
||||
i = end;
|
||||
}
|
||||
wire.push(0);
|
||||
|
||||
wire
|
||||
}
|
||||
|
||||
@@ -1475,6 +1483,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_to_wire_escaped_dot_in_label_is_not_a_separator() {
|
||||
// `exa\.mple.com` is two labels: `exa.mple` (8 bytes including the 0x2E) and `com`.
|
||||
let wire = name_to_wire("exa\\.mple.com");
|
||||
assert_eq!(
|
||||
wire,
|
||||
vec![8, b'e', b'x', b'a', b'.', b'm', b'p', b'l', b'e', 3, b'c', b'o', b'm', 0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_to_wire_decimal_escape_is_lowercased() {
|
||||
// \065 = 'A', must become 'a' in canonical form.
|
||||
let wire = name_to_wire("\\065bc.com");
|
||||
assert_eq!(wire, vec![3, b'a', b'b', b'c', 3, b'c', b'o', b'm', 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parent_zone_cases() {
|
||||
assert_eq!(parent_zone("example.com"), "com");
|
||||
|
||||
@@ -2,6 +2,17 @@ use std::net::SocketAddr;
|
||||
|
||||
use log::info;
|
||||
|
||||
fn print_recursive_hint() {
|
||||
let is_recursive = crate::config::load_config("numa.toml")
|
||||
.map(|c| c.config.upstream.mode == crate::config::UpstreamMode::Recursive)
|
||||
.unwrap_or(false);
|
||||
if !is_recursive {
|
||||
eprintln!(" Want full DNS sovereignty? Add to numa.toml:");
|
||||
eprintln!(" [upstream]");
|
||||
eprintln!(" mode = \"recursive\"\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn is_loopback_or_stub(addr: &str) -> bool {
|
||||
matches!(addr, "127.0.0.1" | "127.0.0.53" | "0.0.0.0" | "::1" | "")
|
||||
}
|
||||
@@ -688,9 +699,7 @@ fn install_windows() -> Result<(), String> {
|
||||
} else {
|
||||
eprintln!(" Numa will start automatically on next boot.\n");
|
||||
}
|
||||
eprintln!(" Want full DNS sovereignty? Add to numa.toml:");
|
||||
eprintln!(" [upstream]");
|
||||
eprintln!(" mode = \"recursive\"\n");
|
||||
print_recursive_hint();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1181,9 +1190,7 @@ fn install_service_macos() -> Result<(), String> {
|
||||
eprintln!(" Numa will auto-start on boot and restart if killed.");
|
||||
eprintln!(" Logs: /usr/local/var/log/numa.log");
|
||||
eprintln!(" Run 'sudo numa uninstall' to restore original DNS.\n");
|
||||
eprintln!(" Want full DNS sovereignty? Add to numa.toml:");
|
||||
eprintln!(" [upstream]");
|
||||
eprintln!(" mode = \"recursive\"\n");
|
||||
print_recursive_hint();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1388,9 +1395,7 @@ fn install_service_linux() -> Result<(), String> {
|
||||
eprintln!(" Numa will auto-start on boot and restart if killed.");
|
||||
eprintln!(" Logs: journalctl -u numa -f");
|
||||
eprintln!(" Run 'sudo numa uninstall' to restore original DNS.\n");
|
||||
eprintln!(" Want full DNS sovereignty? Add to numa.toml:");
|
||||
eprintln!(" [upstream]");
|
||||
eprintln!(" mode = \"recursive\"\n");
|
||||
print_recursive_hint();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
34
src/tls.rs
34
src/tls.rs
@@ -5,7 +5,9 @@ use std::sync::Arc;
|
||||
use log::{info, warn};
|
||||
|
||||
use crate::ctx::ServerCtx;
|
||||
use rcgen::{BasicConstraints, CertificateParams, DnType, IsCa, KeyPair, KeyUsagePurpose, SanType};
|
||||
use rcgen::{
|
||||
BasicConstraints, CertificateParams, DnType, IsCa, Issuer, KeyPair, KeyUsagePurpose, SanType,
|
||||
};
|
||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||
use rustls::ServerConfig;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
@@ -87,8 +89,8 @@ pub fn build_tls_config(
|
||||
alpn: Vec<Vec<u8>>,
|
||||
data_dir: &Path,
|
||||
) -> crate::Result<Arc<ServerConfig>> {
|
||||
let (ca_cert, ca_key) = ensure_ca(data_dir)?;
|
||||
let (cert_chain, key) = generate_service_cert(&ca_cert, &ca_key, tld, service_names)?;
|
||||
let (ca_der, issuer) = ensure_ca(data_dir)?;
|
||||
let (cert_chain, key) = generate_service_cert(&ca_der, &issuer, tld, service_names)?;
|
||||
|
||||
// Ensure a crypto provider is installed (rustls needs one)
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
@@ -106,7 +108,7 @@ pub fn build_tls_config(
|
||||
Ok(Arc::new(config))
|
||||
}
|
||||
|
||||
fn ensure_ca(dir: &Path) -> crate::Result<(rcgen::Certificate, KeyPair)> {
|
||||
fn ensure_ca(dir: &Path) -> crate::Result<(CertificateDer<'static>, Issuer<'static, KeyPair>)> {
|
||||
let ca_key_path = dir.join("ca.key");
|
||||
let ca_cert_path = dir.join(CA_FILE_NAME);
|
||||
|
||||
@@ -114,10 +116,12 @@ fn ensure_ca(dir: &Path) -> crate::Result<(rcgen::Certificate, KeyPair)> {
|
||||
let key_pem = std::fs::read_to_string(&ca_key_path)?;
|
||||
let cert_pem = std::fs::read_to_string(&ca_cert_path)?;
|
||||
let key_pair = KeyPair::from_pem(&key_pem)?;
|
||||
let params = CertificateParams::from_ca_cert_pem(&cert_pem)?;
|
||||
let cert = params.self_signed(&key_pair)?;
|
||||
let ca_der = rustls_pemfile::certs(&mut cert_pem.as_bytes())
|
||||
.next()
|
||||
.ok_or("empty CA PEM file")??;
|
||||
let issuer = Issuer::from_ca_cert_der(&ca_der, key_pair)?;
|
||||
info!("loaded CA from {:?}", ca_cert_path);
|
||||
return Ok((cert, key_pair));
|
||||
return Ok((ca_der, issuer));
|
||||
}
|
||||
|
||||
// Generate new CA
|
||||
@@ -145,14 +149,16 @@ fn ensure_ca(dir: &Path) -> crate::Result<(rcgen::Certificate, KeyPair)> {
|
||||
}
|
||||
|
||||
info!("generated CA at {:?}", ca_cert_path);
|
||||
Ok((cert, key_pair))
|
||||
let ca_der = cert.der().clone();
|
||||
let issuer = Issuer::new(params, key_pair);
|
||||
Ok((ca_der, issuer))
|
||||
}
|
||||
|
||||
/// Generate a cert with explicit SANs for each service name.
|
||||
/// Always regenerated at startup (~5ms) — no disk caching needed.
|
||||
fn generate_service_cert(
|
||||
ca_cert: &rcgen::Certificate,
|
||||
ca_key: &KeyPair,
|
||||
ca_der: &CertificateDer<'static>,
|
||||
issuer: &Issuer<'_, KeyPair>,
|
||||
tld: &str,
|
||||
service_names: &[String],
|
||||
) -> crate::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
||||
@@ -187,7 +193,7 @@ fn generate_service_cert(
|
||||
params.not_before = OffsetDateTime::now_utc();
|
||||
params.not_after = OffsetDateTime::now_utc() + Duration::days(CERT_VALIDITY_DAYS);
|
||||
|
||||
let cert = params.signed_by(&key_pair, ca_cert, ca_key)?;
|
||||
let cert = params.signed_by(&key_pair, issuer)?;
|
||||
|
||||
info!(
|
||||
"generated TLS cert for: {}",
|
||||
@@ -198,11 +204,11 @@ fn generate_service_cert(
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
let cert_der = CertificateDer::from(cert.der().to_vec());
|
||||
let ca_der = CertificateDer::from(ca_cert.der().to_vec());
|
||||
let cert_der = cert.der().clone();
|
||||
let ca_cert_der = ca_der.clone();
|
||||
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key_pair.serialize_der()));
|
||||
|
||||
Ok((vec![cert_der, ca_der], key_der))
|
||||
Ok((vec![cert_der, ca_cert_der], key_der))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user