mirror of
https://github.com/DNSCrypt/doh-server.git
synced 2025-04-07 06:57:35 +03:00
Compare commits
105 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f0242354d3 | ||
|
25fa6946e6 | ||
|
2254632d33 | ||
|
672d1a11f1 | ||
|
9e4a931bce | ||
|
40b0b02972 | ||
|
bf443c33b9 | ||
|
1a0a0566c4 | ||
|
890a74276f | ||
|
34f614e938 | ||
|
d6635eebb7 | ||
|
c79501aea3 | ||
|
e73964fa1d | ||
|
bafbdc0926 | ||
|
30a55a0f2f | ||
|
7bb8293c28 | ||
|
a6517472d5 | ||
|
3511672d49 | ||
|
bd85572368 | ||
|
02b3a67a00 | ||
|
66c66c7a28 | ||
|
1165fab90c | ||
|
c92308ccbb | ||
|
78c47830ff | ||
|
9e2853da86 | ||
|
e5f6f2a5d6 | ||
|
e8df0458ac | ||
|
19040f1e88 | ||
|
6f9f63e754 | ||
|
678bd04bed | ||
|
ffa0828515 | ||
|
6580f6ffb5 | ||
|
f64770bdd7 | ||
|
18297228c7 | ||
|
908e7d64db | ||
|
c54b3303fc | ||
|
1c5c83803a | ||
|
1386b7d13a | ||
|
920d31b502 | ||
|
651224d900 | ||
|
b5d525abcd | ||
|
11d8f4cb31 | ||
|
47330ebcad | ||
|
d5fd8231ff | ||
|
8cba04338e | ||
|
85280f4525 | ||
|
1c28a28b78 | ||
|
fbf82068d1 | ||
|
c9e084b2b4 | ||
|
37dc663b6e | ||
|
b81cc3e5d2 | ||
|
3f1bbcd8dc | ||
|
e92fddb165 | ||
|
d573a20c86 | ||
|
f5c07a205b | ||
|
d277c0a806 | ||
|
fc61c79a9f | ||
|
a92f4a77ae | ||
|
a373957045 | ||
|
6f5213838b | ||
|
eede3f4ab3 | ||
|
fdcc797fcb | ||
|
3e59f42558 | ||
|
a1fc5bbffc | ||
|
4b887d6705 | ||
|
6818fbe8a1 | ||
|
c82fb339ed | ||
|
06a3fa0499 | ||
|
8b9f9377b3 | ||
|
767b3e17b1 | ||
|
a60ced8782 | ||
|
25d1261730 | ||
|
ff62b6a24b | ||
|
fd65582aa6 | ||
|
d12b9deb35 | ||
|
965bca7fde | ||
|
5b11bc520e | ||
|
ab4c27ef86 | ||
|
db9c8634e3 | ||
|
533c29ec1e | ||
|
e27ab7dee9 | ||
|
511b0b4388 | ||
|
74939bdc6c | ||
|
054beb390c | ||
|
16ab626cc2 | ||
|
115938f90f | ||
|
c6c9d64681 | ||
|
d586c50019 | ||
|
46be8b9662 | ||
|
e6fe51647d | ||
|
379a7abc7e | ||
|
5770f9da33 | ||
|
b77f10cd9d | ||
|
63eac2a622 | ||
|
a727c4b9fa | ||
|
2918061786 | ||
|
7657d5a2b2 | ||
|
f9d2a0fc94 | ||
|
4f1e0f2abe | ||
|
a988eb42a2 | ||
|
a19c523cf2 | ||
|
b637bb1ec9 | ||
|
f4a1dee971 | ||
|
f4cc9bb0f9 | ||
|
485afd5976 |
19 changed files with 613 additions and 350 deletions
17
.github/workflows/issues.yml
vendored
Normal file
17
.github/workflows/issues.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
118
.github/workflows/release.yml
vendored
118
.github/workflows/release.yml
vendored
|
@ -14,7 +14,11 @@ jobs:
|
|||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
|
||||
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: mlugg/setup-zig@v1
|
||||
with:
|
||||
version: 0.10.1
|
||||
|
||||
- uses: hecrj/setup-rust-action@master
|
||||
with:
|
||||
|
@ -27,18 +31,60 @@ jobs:
|
|||
run: rustup default | grep stable
|
||||
|
||||
- name: Install cargo-deb
|
||||
run: cargo install --debug cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
|
||||
- name: Release build
|
||||
- name: Install cargo-generate-rpm
|
||||
run: cargo install cargo-generate-rpm
|
||||
|
||||
- name: Install cargo-zigbuild
|
||||
run: cargo install cargo-zigbuild
|
||||
|
||||
- name: Release build Linux-x86-64
|
||||
run: |
|
||||
env RUSTFLAGS="-C link-arg=-s" cargo build --release
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
env RUSTFLAGS="-C strip=symbols" cargo zigbuild --release --target x86_64-unknown-linux-musl
|
||||
mkdir doh-proxy
|
||||
mv target/release/doh-proxy doh-proxy/
|
||||
mv target/x86_64-unknown-linux-musl/release/doh-proxy doh-proxy/
|
||||
cp README.md localhost.pem doh-proxy/
|
||||
tar cJpf doh-proxy_${{ steps.get_version.outputs.VERSION }}_linux-x86_64.tar.bz2 doh-proxy
|
||||
- name: Debian package
|
||||
tar cjpf doh-proxy_${{ steps.get_version.outputs.VERSION }}_linux-x86_64.tar.bz2 doh-proxy
|
||||
rm -fr doh-proxy
|
||||
|
||||
- name: Release build Linux-aarch64
|
||||
run: |
|
||||
cargo deb
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
env RUSTFLAGS="-C strip=symbols" cargo zigbuild --release --target aarch64-unknown-linux-musl
|
||||
mkdir doh-proxy
|
||||
mv target/aarch64-unknown-linux-musl/release/doh-proxy doh-proxy/
|
||||
cp README.md localhost.pem doh-proxy/
|
||||
tar cjpf doh-proxy_${{ steps.get_version.outputs.VERSION }}_linux-aarch64.tar.bz2 doh-proxy
|
||||
rm -fr doh-proxy
|
||||
|
||||
- name: Release build Windows-x86_64
|
||||
run: |
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
env RUSTFLAGS="-C strip=symbols" cargo zigbuild --release --target x86_64-pc-windows-gnu
|
||||
mkdir doh-proxy
|
||||
mv target/x86_64-pc-windows-gnu/release/doh-proxy.exe doh-proxy/
|
||||
cp README.md localhost.pem doh-proxy/
|
||||
zip -9 -r doh-proxy_${{ steps.get_version.outputs.VERSION }}_windows-x86_64.zip doh-proxy
|
||||
rm -fr doh-proxy
|
||||
|
||||
- name: Debian packages
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
env RUSTFLAGS="-C strip=symbols" cargo deb --no-strip --cargo-build=zigbuild --target=x86_64-unknown-linux-musl
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
env RUSTFLAGS="-C strip=symbols" cargo deb --no-strip --cargo-build=zigbuild --target=aarch64-unknown-linux-musl
|
||||
|
||||
- name: RPM packages
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-gnu
|
||||
env RUSTFLAGS="-C strip=symbols" cargo-zigbuild build --target=x86_64-unknown-linux-gnu.2.17 --release
|
||||
mv target/x86_64-unknown-linux-musl/release/doh-proxy target/release/
|
||||
cargo generate-rpm --target x86_64-unknown-linux-gnu
|
||||
rustup target add aarch64-unknown-linux-gnu
|
||||
env RUSTFLAGS="-C strip=symbols" cargo-zigbuild build --target=aarch64-unknown-linux-gnu.2.17 --release
|
||||
cargo generate-rpm --target aarch64-unknown-linux-gnu
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
|
@ -51,19 +97,41 @@ jobs:
|
|||
draft: true
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Debian package
|
||||
id: upload-release-asset-debian
|
||||
- name: Upload Debian package for x86_64
|
||||
id: upload-release-asset-debian-x86_64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_name: "doh-proxy_${{ steps.get_version.outputs.VERSION }}_amd64.deb"
|
||||
asset_path: "target/debian/doh-proxy_${{ steps.get_version.outputs.VERSION }}_amd64.deb"
|
||||
asset_name: "doh-proxy_${{ steps.get_version.outputs.VERSION }}-1_amd64.deb"
|
||||
asset_path: "target/x86_64-unknown-linux-musl/debian/doh-proxy_${{ steps.get_version.outputs.VERSION }}-1_amd64.deb"
|
||||
asset_content_type: application/x-debian-package
|
||||
|
||||
- name: Upload tarball
|
||||
id: upload-release-asset-tarball
|
||||
- name: Upload RPM package for x86_64
|
||||
id: upload-release-asset-rpm-x86_64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_name: "doh-proxy-${{ steps.get_version.outputs.VERSION }}-1.x86_64.rpm"
|
||||
asset_path: "target/x86_64-unknown-linux-gnu/generate-rpm/doh-proxy-${{ steps.get_version.outputs.VERSION }}-1.x86_64.rpm"
|
||||
asset_content_type: application/x-redhat-package-manager
|
||||
|
||||
- name: Upload RPM package for aarch64
|
||||
id: upload-release-asset-rpm-aarch64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_name: "doh-proxy-${{ steps.get_version.outputs.VERSION }}-1.aarch64.rpm"
|
||||
asset_path: "target/aarch64-unknown-linux-gnu/generate-rpm/doh-proxy-${{ steps.get_version.outputs.VERSION }}-1.aarch64.rpm"
|
||||
asset_content_type: application/x-redhat-package-manager
|
||||
|
||||
- name: Upload tarball for linux-x86_64
|
||||
id: upload-release-asset-tarball-linux-x86_64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -72,3 +140,25 @@ jobs:
|
|||
asset_name: "doh-proxy_${{ steps.get_version.outputs.VERSION }}_linux-x86_64.tar.bz2"
|
||||
asset_path: "doh-proxy_${{ steps.get_version.outputs.VERSION }}_linux-x86_64.tar.bz2"
|
||||
asset_content_type: application/x-tar
|
||||
|
||||
- name: Upload tarball for linux-aarch64
|
||||
id: upload-release-asset-tarball-linux-aarch64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_name: "doh-proxy_${{ steps.get_version.outputs.VERSION }}_linux-aarch64.tar.bz2"
|
||||
asset_path: "doh-proxy_${{ steps.get_version.outputs.VERSION }}_linux-aarch64.tar.bz2"
|
||||
asset_content_type: application/x-tar
|
||||
|
||||
- name: Upload tarball for windows-x86_64
|
||||
id: upload-release-asset-tarball-windows-x86_64
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_name: "doh-proxy_${{ steps.get_version.outputs.VERSION }}_windows-x86_64.zip"
|
||||
asset_path: "doh-proxy_${{ steps.get_version.outputs.VERSION }}_windows-x86_64.zip"
|
||||
asset_content_type: application/zip
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
- stable
|
30
Cargo.toml
30
Cargo.toml
|
@ -1,9 +1,9 @@
|
|||
[package]
|
||||
name = "doh-proxy"
|
||||
version = "0.4.0"
|
||||
version = "0.9.11"
|
||||
authors = ["Frank Denis <github@pureftpd.org>"]
|
||||
description = "A DNS-over-HTTPS (DoH) and ODoH (Oblivious DoH) proxy"
|
||||
keywords = ["dns","https","doh","odoh","proxy"]
|
||||
keywords = ["dns", "https", "doh", "odoh", "proxy"]
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/jedisct1/rust-doh"
|
||||
repository = "https://github.com/jedisct1/rust-doh"
|
||||
|
@ -16,17 +16,31 @@ default = ["tls"]
|
|||
tls = ["libdoh/tls"]
|
||||
|
||||
[dependencies]
|
||||
libdoh = { path = "src/libdoh", version = "0.4.0", default-features = false }
|
||||
clap = "2.33.3"
|
||||
dnsstamps = "0.1.5"
|
||||
jemallocator = "0.3.2"
|
||||
libdoh = { path = "src/libdoh", version = "0.9.9", default-features = false }
|
||||
clap = { version = "4", features = ["std", "cargo", "wrap_help", "string"] }
|
||||
dnsstamps = "0.1.10"
|
||||
mimalloc = { version = "0.1.44", default-features = false }
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [
|
||||
{ source = "target/release/doh-proxy", dest = "/usr/bin/doh-proxy", mode = "755" },
|
||||
{ source = "README.md", dest = "/usr/share/doc/doh-proxy/README.md", mode = "644", doc = true },
|
||||
]
|
||||
|
||||
[package.metadata.deb]
|
||||
extended-description = """\
|
||||
A fast and secure DoH (DNS-over-HTTPS) and ODoH server written in Rust."""
|
||||
assets = [
|
||||
["target/release/doh-proxy", "usr/bin/", "755"],
|
||||
["README.md", "usr/share/doc/doh-proxy/README.md", "644"]
|
||||
[
|
||||
"target/release/doh-proxy",
|
||||
"usr/bin/",
|
||||
"755",
|
||||
],
|
||||
[
|
||||
"README.md",
|
||||
"usr/share/doc/doh-proxy/README.md",
|
||||
"644",
|
||||
],
|
||||
]
|
||||
section = "network"
|
||||
depends = "$auto"
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Frank Denis
|
||||
Copyright (c) 2018-2025 Frank Denis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
34
README.md
34
README.md
|
@ -1,4 +1,4 @@
|
|||
# doh-proxy
|
||||
# 
|
||||
|
||||
A fast and secure DoH (DNS-over-HTTPS) and ODoH (Oblivious DoH) server.
|
||||
|
||||
|
@ -33,7 +33,7 @@ USAGE:
|
|||
doh-proxy [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-O, --allow-odoh-post Allow POST queries over ODoH even with they have been disabed for DoH
|
||||
-O, --allow-odoh-post Allow POST queries over ODoH even if they have been disabed for DoH
|
||||
-K, --disable-keepalive Disable keepalive
|
||||
-P, --disable-post Disable POST queries
|
||||
-h, --help Prints help information
|
||||
|
@ -50,6 +50,7 @@ OPTIONS:
|
|||
-T, --min-ttl <min_ttl> Minimum TTL, in seconds [default: 10]
|
||||
-p, --path <path> URI path [default: /dns-query]
|
||||
-g, --public-address <public_address> External IP address DoH clients will connect to
|
||||
-j, --public-port <public_port> External port DoH clients will connect to, if not 443
|
||||
-u, --server-address <server_address> Address to connect to [default: 9.9.9.9:53]
|
||||
-t, --timeout <timeout> Timeout, in seconds [default: 10]
|
||||
-I, --tls-cert-key-path <tls_cert_key_path>
|
||||
|
@ -59,13 +60,21 @@ OPTIONS:
|
|||
Path to the PEM/PKCS#8-encoded certificates (only required for built-in TLS)
|
||||
```
|
||||
|
||||
## HTTP/2 termination
|
||||
Example command-line:
|
||||
|
||||
The recommended way to use `doh-proxy` is to use a TLS termination proxy (such as [hitch](https://github.com/varnish/hitch) or [relayd](https://bsd.plumbing/about.html)), a CDN or a web server with proxying abilities as a front-end.
|
||||
```sh
|
||||
doh-proxy -H 'doh.example.com' -u 127.0.0.1:53 -g 233.252.0.5
|
||||
```
|
||||
|
||||
Here, `doh.example.com` is the host name (which should match a name included in the TLS certificate), `127.0.0.1:53` is the address of the DNS resolver, and `233.252.0.5` is the public IP address of the DoH server.
|
||||
|
||||
## HTTP/2 and HTTP/3 termination
|
||||
|
||||
The recommended way to use `doh-proxy` is to use a TLS termination proxy (such as [hitch](https://github.com/varnish/hitch) or [relayd](https://man.openbsd.org/relayd.8)), a CDN or a web server with proxying abilities as a front-end.
|
||||
|
||||
That way, the DoH service can be exposed as a virtual host, sharing the same IP addresses as existing websites.
|
||||
|
||||
If `doh-proxy` and the HTTP/2 front-end run on the same host, using the HTTP protocol to communicate between both is fine.
|
||||
If `doh-proxy` and the HTTP/2 (/ HTTP/3) front-end run on the same host, using the HTTP protocol to communicate between both is fine.
|
||||
|
||||
If both are on distinct networks, such as when using a CDN, `doh-proxy` can handle HTTPS requests, provided that it was compiled with the `tls` feature.
|
||||
|
||||
|
@ -127,7 +136,7 @@ This can be achieved with the `--allow-odoh-post` command-line switch.
|
|||
* When using DoH, DNS stamps should include a resolver IP address in order to remove a dependency on non-encrypted, non-authenticated, easy-to-block resolvers.
|
||||
* Unlike DNSCrypt where users must explicitly trust a DNS server's public key, the security of DoH relies on traditional public Certificate Authorities. Additional root certificates (required by governments, security software, enterprise gateways) installed on a client immediately make DoH vulnerable to MITM. In order to prevent this, DNS stamps should include the hash of the parent certificate.
|
||||
* TLS certificates are tied to host names. But domains expire, get reassigned and switch hands all the time. If a domain originally used for a DoH service gets a new, possibly malicious owner, clients still configured to use the service will blindly keep trusting it if the CA is the same. As a mitigation, the CA should sign an intermediate certificate (the only one present in the stamp), itself used to sign the name used by the DoH server. While commercial CAs offer this, Let's Encrypt currently doesn't.
|
||||
* Make sure that the front-end supports HTTP/2 and TLS 1.3.
|
||||
* Make sure that the front-end supports at least HTTP/2 and TLS 1.3.
|
||||
* Internal DoH servers still require TLS certificates. So, if you are planning to deploy an internal server, you need to set up an internal CA, or add self-signed certificates to every single client.
|
||||
|
||||
## Example usage with `encrypted-dns-server`
|
||||
|
@ -141,10 +150,10 @@ upstream_addr = "127.0.0.1:3000"
|
|||
|
||||
## Example usage with `nginx`
|
||||
|
||||
In an existing `server`, a `/doh` endpoint can be exposed that way:
|
||||
In an existing `server`, a `/dns-query` endpoint can be exposed that way:
|
||||
|
||||
```text
|
||||
location /doh {
|
||||
location /dns-query {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
}
|
||||
```
|
||||
|
@ -186,11 +195,14 @@ This [Go code snippet](https://gist.github.com/d6cb41742a1ceb54d48cc286f3d5c5fa)
|
|||
|
||||
### Common certificate hashes
|
||||
|
||||
* Let's Encrypt R3:
|
||||
* `3286ff65a65faf32085eea1388c3738ba7e37873c906cce3c4a28b4cc2a58988` and
|
||||
* `444ebd67bb83f8807b3921e938ac9178b882bd50aadb11231f044cf5f08df7ce`
|
||||
* Let's Encrypt E1:
|
||||
* `cc1060d39c8329b62b6fbc7d0d6df9309869b981e7e6392d5cd8fa408f4d80e6`
|
||||
* Let's Encrypt R3:
|
||||
* `444ebd67bb83f8807b3921e938ac9178b882bd50aadb11231f044cf5f08df7ce`
|
||||
* Let's Encrypt R10:
|
||||
* `e644ba6963e335fe765cb9976b12b10eb54294b42477764ccb3a3acca3acb2fc`
|
||||
* ZeroSSL:
|
||||
* `9a3a34f727deb9bca51003d9ce9c39f8f27dd9c5242901c2bab1a44e635a0219`
|
||||
|
||||
## Clients
|
||||
|
||||
|
|
BIN
logo.png
Normal file
BIN
logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
247
src/config.rs
247
src/config.rs
|
@ -1,14 +1,13 @@
|
|||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
|
||||
#[cfg(feature = "tls")]
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{Arg, ArgAction::SetTrue};
|
||||
use libdoh::*;
|
||||
|
||||
use crate::constants::*;
|
||||
|
||||
use clap::Arg;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn parse_opts(globals: &mut Globals) {
|
||||
use crate::utils::{verify_remote_server, verify_sock_addr};
|
||||
|
||||
|
@ -20,118 +19,128 @@ pub fn parse_opts(globals: &mut Globals) {
|
|||
let err_ttl = ERR_TTL.to_string();
|
||||
|
||||
let _ = include_str!("../Cargo.toml");
|
||||
let options = app_from_crate!()
|
||||
let options = command!()
|
||||
.arg(
|
||||
Arg::with_name("hostname")
|
||||
.short("H")
|
||||
Arg::new("hostname")
|
||||
.short('H')
|
||||
.long("hostname")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.help("Host name (not IP address) DoH clients will use to connect"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("public_address")
|
||||
.short("g")
|
||||
Arg::new("public_address")
|
||||
.short('g')
|
||||
.long("public-address")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.help("External IP address DoH clients will connect to"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("listen_address")
|
||||
.short("l")
|
||||
Arg::new("public_port")
|
||||
.short('j')
|
||||
.long("public-port")
|
||||
.num_args(1)
|
||||
.help("External port DoH clients will connect to, if not 443"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("listen_address")
|
||||
.short('l')
|
||||
.long("listen-address")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.default_value(LISTEN_ADDRESS)
|
||||
.validator(verify_sock_addr)
|
||||
.value_parser(verify_sock_addr)
|
||||
.help("Address to listen to"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("server_address")
|
||||
.short("u")
|
||||
Arg::new("server_address")
|
||||
.short('u')
|
||||
.long("server-address")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.default_value(SERVER_ADDRESS)
|
||||
.validator(verify_remote_server)
|
||||
.value_parser(verify_remote_server)
|
||||
.help("Address to connect to"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("local_bind_address")
|
||||
.short("b")
|
||||
Arg::new("local_bind_address")
|
||||
.short('b')
|
||||
.long("local-bind-address")
|
||||
.takes_value(true)
|
||||
.validator(verify_sock_addr)
|
||||
.num_args(1)
|
||||
.value_parser(verify_sock_addr)
|
||||
.help("Address to connect from"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("path")
|
||||
.short("p")
|
||||
Arg::new("path")
|
||||
.short('p')
|
||||
.long("path")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.default_value(PATH)
|
||||
.help("URI path"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max_clients")
|
||||
.short("c")
|
||||
Arg::new("max_clients")
|
||||
.short('c')
|
||||
.long("max-clients")
|
||||
.takes_value(true)
|
||||
.default_value(&max_clients)
|
||||
.num_args(1)
|
||||
.default_value(max_clients)
|
||||
.help("Maximum number of simultaneous clients"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max_concurrent")
|
||||
.short("C")
|
||||
Arg::new("max_concurrent")
|
||||
.short('C')
|
||||
.long("max-concurrent")
|
||||
.takes_value(true)
|
||||
.default_value(&max_concurrent_streams)
|
||||
.num_args(1)
|
||||
.default_value(max_concurrent_streams)
|
||||
.help("Maximum number of concurrent requests per client"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("timeout")
|
||||
.short("t")
|
||||
Arg::new("timeout")
|
||||
.short('t')
|
||||
.long("timeout")
|
||||
.takes_value(true)
|
||||
.default_value(&timeout_sec)
|
||||
.num_args(1)
|
||||
.default_value(timeout_sec)
|
||||
.help("Timeout, in seconds"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("min_ttl")
|
||||
.short("T")
|
||||
Arg::new("min_ttl")
|
||||
.short('T')
|
||||
.long("min-ttl")
|
||||
.takes_value(true)
|
||||
.default_value(&min_ttl)
|
||||
.num_args(1)
|
||||
.default_value(min_ttl)
|
||||
.help("Minimum TTL, in seconds"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max_ttl")
|
||||
.short("X")
|
||||
Arg::new("max_ttl")
|
||||
.short('X')
|
||||
.long("max-ttl")
|
||||
.takes_value(true)
|
||||
.default_value(&max_ttl)
|
||||
.num_args(1)
|
||||
.default_value(max_ttl)
|
||||
.help("Maximum TTL, in seconds"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("err_ttl")
|
||||
.short("E")
|
||||
Arg::new("err_ttl")
|
||||
.short('E')
|
||||
.long("err-ttl")
|
||||
.takes_value(true)
|
||||
.default_value(&err_ttl)
|
||||
.num_args(1)
|
||||
.default_value(err_ttl)
|
||||
.help("TTL for errors, in seconds"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("disable_keepalive")
|
||||
.short("K")
|
||||
Arg::new("disable_keepalive")
|
||||
.short('K')
|
||||
.action(SetTrue)
|
||||
.long("disable-keepalive")
|
||||
.help("Disable keepalive"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("disable_post")
|
||||
.short("P")
|
||||
Arg::new("disable_post")
|
||||
.short('P')
|
||||
.action(SetTrue)
|
||||
.long("disable-post")
|
||||
.help("Disable POST queries"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("allow_odoh_post")
|
||||
.short("O")
|
||||
Arg::new("allow_odoh_post")
|
||||
.short('O')
|
||||
.action(SetTrue)
|
||||
.long("allow-odoh-post")
|
||||
.help("Allow POST queries over ODoH even if they have been disabed for DoH"),
|
||||
);
|
||||
|
@ -139,33 +148,36 @@ pub fn parse_opts(globals: &mut Globals) {
|
|||
#[cfg(feature = "tls")]
|
||||
let options = options
|
||||
.arg(
|
||||
Arg::with_name("tls_cert_path")
|
||||
.short("i")
|
||||
Arg::new("tls_cert_path")
|
||||
.short('i')
|
||||
.long("tls-cert-path")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.help(
|
||||
"Path to the PEM/PKCS#8-encoded certificates (only required for built-in TLS)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("tls_cert_key_path")
|
||||
.short("I")
|
||||
Arg::new("tls_cert_key_path")
|
||||
.short('I')
|
||||
.long("tls-cert-key-path")
|
||||
.takes_value(true)
|
||||
.num_args(1)
|
||||
.help("Path to the PEM-encoded secret keys (only required for built-in TLS)"),
|
||||
);
|
||||
|
||||
let matches = options.get_matches();
|
||||
globals.listen_address = matches.value_of("listen_address").unwrap().parse().unwrap();
|
||||
|
||||
globals.listen_address = matches
|
||||
.get_one::<String>("listen_address")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
globals.server_address = matches
|
||||
.value_of("server_address")
|
||||
.get_one::<String>("server_address")
|
||||
.unwrap()
|
||||
.to_socket_addrs()
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
globals.local_bind_address = match matches.value_of("local_bind_address") {
|
||||
globals.local_bind_address = match matches.get_one::<String>("local_bind_address") {
|
||||
Some(address) => address.parse().unwrap(),
|
||||
None => match globals.server_address {
|
||||
SocketAddr::V4(_) => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),
|
||||
|
@ -177,40 +189,93 @@ pub fn parse_opts(globals: &mut Globals) {
|
|||
)),
|
||||
},
|
||||
};
|
||||
globals.path = matches.value_of("path").unwrap().to_string();
|
||||
globals.path = matches.get_one::<String>("path").unwrap().to_string();
|
||||
if !globals.path.starts_with('/') {
|
||||
globals.path = format!("/{}", globals.path);
|
||||
}
|
||||
globals.max_clients = matches.value_of("max_clients").unwrap().parse().unwrap();
|
||||
globals.timeout = Duration::from_secs(matches.value_of("timeout").unwrap().parse().unwrap());
|
||||
globals.max_concurrent_streams = matches.value_of("max_concurrent").unwrap().parse().unwrap();
|
||||
globals.min_ttl = matches.value_of("min_ttl").unwrap().parse().unwrap();
|
||||
globals.max_ttl = matches.value_of("max_ttl").unwrap().parse().unwrap();
|
||||
globals.err_ttl = matches.value_of("err_ttl").unwrap().parse().unwrap();
|
||||
globals.keepalive = !matches.is_present("disable_keepalive");
|
||||
globals.disable_post = matches.is_present("disable_post");
|
||||
globals.allow_odoh_post = matches.is_present("allow_odoh_post");
|
||||
globals.max_clients = matches
|
||||
.get_one::<String>("max_clients")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
globals.timeout = Duration::from_secs(
|
||||
matches
|
||||
.get_one::<String>("timeout")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
globals.max_concurrent_streams = matches
|
||||
.get_one::<String>("max_concurrent")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
globals.min_ttl = matches
|
||||
.get_one::<String>("min_ttl")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
globals.max_ttl = matches
|
||||
.get_one::<String>("max_ttl")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
globals.err_ttl = matches
|
||||
.get_one::<String>("err_ttl")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
globals.keepalive = !matches.get_flag("disable_keepalive");
|
||||
globals.disable_post = matches.get_flag("disable_post");
|
||||
globals.allow_odoh_post = matches.get_flag("allow_odoh_post");
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
{
|
||||
globals.tls_cert_path = matches.value_of("tls_cert_path").map(PathBuf::from);
|
||||
globals.tls_cert_path = matches
|
||||
.get_one::<String>("tls_cert_path")
|
||||
.map(PathBuf::from);
|
||||
globals.tls_cert_key_path = matches
|
||||
.value_of("tls_cert_key_path")
|
||||
.get_one::<String>("tls_cert_key_path")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| globals.tls_cert_path.clone());
|
||||
}
|
||||
|
||||
if let Some(hostname) = matches.value_of("hostname") {
|
||||
let mut builder =
|
||||
dnsstamps::DoHBuilder::new(hostname.to_string(), globals.path.to_string());
|
||||
if let Some(public_address) = matches.value_of("public_address") {
|
||||
builder = builder.with_address(public_address.to_string());
|
||||
match matches.get_one::<String>("hostname") {
|
||||
Some(hostname) => {
|
||||
let mut builder =
|
||||
dnsstamps::DoHBuilder::new(hostname.to_string(), globals.path.to_string());
|
||||
if let Some(public_address) = matches.get_one::<String>("public_address") {
|
||||
builder = builder.with_address(public_address.to_string());
|
||||
}
|
||||
if let Some(public_port) = matches.get_one::<String>("public_port") {
|
||||
let public_port = public_port.parse().expect("Invalid public port");
|
||||
builder = builder.with_port(public_port);
|
||||
}
|
||||
println!(
|
||||
"Test DNS stamp to reach [{}] over DoH: [{}]\n",
|
||||
hostname,
|
||||
builder.serialize().unwrap()
|
||||
);
|
||||
|
||||
let mut builder =
|
||||
dnsstamps::ODoHTargetBuilder::new(hostname.to_string(), globals.path.to_string());
|
||||
if let Some(public_port) = matches.get_one::<String>("public_port") {
|
||||
let public_port = public_port.parse().expect("Invalid public port");
|
||||
builder = builder.with_port(public_port);
|
||||
}
|
||||
println!(
|
||||
"Test DNS stamp to reach [{}] over Oblivious DoH: [{}]\n",
|
||||
hostname,
|
||||
builder.serialize().unwrap()
|
||||
);
|
||||
|
||||
println!("Check out https://dnscrypt.info/stamps/ to compute the actual stamps.\n")
|
||||
}
|
||||
println!(
|
||||
"Test DNS stamp to reach [{}]: [{}]",
|
||||
hostname,
|
||||
builder.serialize().unwrap()
|
||||
_ => {
|
||||
println!(
|
||||
"Please provide a fully qualified hostname (-H <hostname> command-line option) to get \
|
||||
test DNS stamps for your server.\n"
|
||||
);
|
||||
println!("Check out https://dnscrypt.info/stamps/ to compute the actual stamp.\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "libdoh"
|
||||
version = "0.4.0"
|
||||
version = "0.9.11"
|
||||
authors = ["Frank Denis <github@pureftpd.org>"]
|
||||
description = "DoH library for the rust-doh app"
|
||||
keywords = ["dns","https","doh","odoh","proxy"]
|
||||
description = "DoH and Oblivious DoH library for the rust-doh app"
|
||||
keywords = ["dns", "https", "doh", "odoh", "proxy"]
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/jedisct1/rust-doh"
|
||||
repository = "https://github.com/jedisct1/rust-doh"
|
||||
categories = ["asynchronous", "network-programming","command-line-utilities"]
|
||||
categories = ["asynchronous", "network-programming", "command-line-utilities"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
|
@ -15,17 +15,31 @@ default = ["tls"]
|
|||
tls = ["tokio-rustls"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.38"
|
||||
byteorder = "1.4.2"
|
||||
base64 = "0.13.0"
|
||||
futures = "0.3.13"
|
||||
hyper = { version = "0.14.4", default-features = false, features = ["server", "http1", "http2", "stream"] }
|
||||
tokio = { version = "1.6.0", features = ["net", "rt-multi-thread", "parking_lot", "time", "sync"] }
|
||||
tokio-rustls = { version = "0.22.0", features = ["early-data"], optional = true }
|
||||
odoh-rs = "0.1.11"
|
||||
rand = "0.7.0"
|
||||
hpke = "0.5.0"
|
||||
arc-swap = "1.2.0"
|
||||
anyhow = "1.0.97"
|
||||
arc-swap = "1.7.1"
|
||||
base64 = "0.22.1"
|
||||
byteorder = "1.5.0"
|
||||
bytes = "1.10.1"
|
||||
futures = "0.3.31"
|
||||
hyper = { version = "^0.14.32", default-features = false, features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"stream",
|
||||
"runtime",
|
||||
] }
|
||||
odoh-rs = "1.0.3"
|
||||
rand = "^0.8.5"
|
||||
tokio = { version = "1.44.1", features = [
|
||||
"net",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
"sync",
|
||||
] }
|
||||
tokio-rustls = { version = "^0.24.1", features = [
|
||||
"early-data",
|
||||
], optional = true }
|
||||
rustls-pemfile = "^1.0.4"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
|
21
src/libdoh/LICENSE
Normal file
21
src/libdoh/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018-2025 Frank Denis
|
||||
|
||||
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.
|
|
@ -6,3 +6,4 @@ pub const STALE_IF_ERROR_SECS: u32 = 86400;
|
|||
pub const STALE_WHILE_REVALIDATE_SECS: u32 = 60;
|
||||
pub const CERTS_WATCH_DELAY_SECS: u32 = 10;
|
||||
pub const ODOH_KEY_ROTATION_SECS: u32 = 86400;
|
||||
pub const UDP_TCP_RATIO: usize = 8;
|
||||
|
|
|
@ -2,9 +2,13 @@ use anyhow::{ensure, Error};
|
|||
use byteorder::{BigEndian, ByteOrder};
|
||||
|
||||
const DNS_HEADER_SIZE: usize = 12;
|
||||
pub const DNS_OFFSET_FLAGS: usize = 2;
|
||||
const DNS_MAX_HOSTNAME_SIZE: usize = 256;
|
||||
const DNS_MAX_PACKET_SIZE: usize = 4096;
|
||||
const DNS_OFFSET_QUESTION: usize = DNS_HEADER_SIZE;
|
||||
|
||||
const DNS_FLAGS_TC: u16 = 1u16 << 9;
|
||||
|
||||
const DNS_TYPE_OPT: u16 = 41;
|
||||
|
||||
const DNS_PTYPE_PADDING: u16 = 12;
|
||||
|
@ -51,6 +55,11 @@ pub fn is_recoverable_error(packet: &[u8]) -> bool {
|
|||
rcode == DNS_RCODE_SERVFAIL || rcode == DNS_RCODE_REFUSED
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_truncated(packet: &[u8]) -> bool {
|
||||
BigEndian::read_u16(&packet[DNS_OFFSET_FLAGS..]) & DNS_FLAGS_TC == DNS_FLAGS_TC
|
||||
}
|
||||
|
||||
fn skip_name(packet: &[u8], offset: usize) -> Result<usize, Error> {
|
||||
let packet_len = packet.len();
|
||||
ensure!(offset < packet_len - 1, "Short packet");
|
||||
|
@ -171,7 +180,7 @@ fn add_edns_section(packet: &mut Vec<u8>, max_payload_size: u16) -> Result<(), E
|
|||
"Packet would be too large to add a new record"
|
||||
);
|
||||
arcount_inc(packet)?;
|
||||
packet.extend(&opt_rr);
|
||||
packet.extend(opt_rr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use hyper::StatusCode;
|
||||
use std::io;
|
||||
|
||||
#[allow(dead_code)]
|
||||
use hyper::StatusCode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DoHError {
|
||||
Incomplete,
|
||||
|
@ -13,6 +13,7 @@ pub enum DoHError {
|
|||
Hyper(hyper::Error),
|
||||
Io(io::Error),
|
||||
ODoHConfigError(anyhow::Error),
|
||||
TooManyTcpSessions,
|
||||
}
|
||||
|
||||
impl std::error::Error for DoHError {}
|
||||
|
@ -26,9 +27,10 @@ impl std::fmt::Display for DoHError {
|
|||
DoHError::UpstreamIssue => write!(fmt, "Upstream error"),
|
||||
DoHError::UpstreamTimeout => write!(fmt, "Upstream timeout"),
|
||||
DoHError::StaleKey => write!(fmt, "Stale key material"),
|
||||
DoHError::Hyper(e) => write!(fmt, "HTTP error: {}", e),
|
||||
DoHError::Io(e) => write!(fmt, "IO error: {}", e),
|
||||
DoHError::ODoHConfigError(e) => write!(fmt, "ODoH config error: {}", e),
|
||||
DoHError::Hyper(e) => write!(fmt, "HTTP error: {e}"),
|
||||
DoHError::Io(e) => write!(fmt, "IO error: {e}"),
|
||||
DoHError::ODoHConfigError(e) => write!(fmt, "ODoH config error: {e}"),
|
||||
DoHError::TooManyTcpSessions => write!(fmt, "Too many TCP sessions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +47,7 @@ impl From<DoHError> for StatusCode {
|
|||
DoHError::Hyper(_) => StatusCode::SERVICE_UNAVAILABLE,
|
||||
DoHError::Io(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
DoHError::ODoHConfigError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
DoHError::TooManyTcpSessions => StatusCode::SERVICE_UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use crate::odoh::ODoHRotator;
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(feature = "tls")]
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::runtime;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
use std::path::PathBuf;
|
||||
use crate::odoh::ODoHRotator;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Globals {
|
||||
|
@ -40,6 +41,10 @@ pub struct Globals {
|
|||
pub struct ClientsCount(Arc<AtomicUsize>);
|
||||
|
||||
impl ClientsCount {
|
||||
pub fn current(&self) -> usize {
|
||||
self.0.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn increment(&self) -> usize {
|
||||
self.0.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
|
|
@ -6,26 +6,38 @@ pub mod odoh;
|
|||
#[cfg(feature = "tls")]
|
||||
mod tls;
|
||||
|
||||
use crate::constants::*;
|
||||
pub use crate::errors::*;
|
||||
pub use crate::globals::*;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use base64::engine::Engine;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use futures::prelude::*;
|
||||
use futures::task::{Context, Poll};
|
||||
use hyper::http;
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::{Body, HeaderMap, Method, Request, Response, StatusCode};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::{TcpListener, UdpSocket};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpSocket, UdpSocket};
|
||||
use tokio::runtime;
|
||||
|
||||
use crate::constants::*;
|
||||
pub use crate::errors::*;
|
||||
pub use crate::globals::*;
|
||||
|
||||
pub mod reexports {
|
||||
pub use tokio;
|
||||
}
|
||||
|
||||
const BASE64_URL_SAFE_NO_PAD: base64::engine::GeneralPurpose =
|
||||
base64::engine::general_purpose::GeneralPurpose::new(
|
||||
&base64::alphabet::URL_SAFE,
|
||||
base64::engine::general_purpose::GeneralPurposeConfig::new()
|
||||
.with_encode_padding(false)
|
||||
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct DnsResponse {
|
||||
packet: Vec<u8>,
|
||||
|
@ -84,9 +96,9 @@ where
|
|||
|
||||
#[allow(clippy::type_complexity)]
|
||||
impl hyper::service::Service<http::Request<Body>> for DoH {
|
||||
type Response = Response<Body>;
|
||||
type Error = http::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
type Response = Response<Body>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
|
@ -117,7 +129,7 @@ impl DoH {
|
|||
match Self::parse_content_type(&req) {
|
||||
Ok(DoHType::Standard) => self.serve_doh_get(req).await,
|
||||
Ok(DoHType::Oblivious) => self.serve_odoh_get(req).await,
|
||||
Err(response) => return Ok(response),
|
||||
Err(response) => Ok(response),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,13 +137,15 @@ impl DoH {
|
|||
match Self::parse_content_type(&req) {
|
||||
Ok(DoHType::Standard) => self.serve_doh_post(req).await,
|
||||
Ok(DoHType::Oblivious) => self.serve_odoh_post(req).await,
|
||||
Err(response) => return Ok(response),
|
||||
Err(response) => Ok(response),
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_doh_query(&self, query: Vec<u8>) -> Result<Response<Body>, http::Error> {
|
||||
let resp = match self.proxy(query).await {
|
||||
Ok(resp) => self.build_response(resp.packet, resp.ttl, DoHType::Standard.as_str()),
|
||||
Ok(resp) => {
|
||||
self.build_response(resp.packet, resp.ttl, DoHType::Standard.as_str(), true)
|
||||
}
|
||||
Err(e) => return http_error(StatusCode::from(e)),
|
||||
};
|
||||
match resp {
|
||||
|
@ -156,9 +170,9 @@ impl DoH {
|
|||
return None;
|
||||
}
|
||||
}
|
||||
let query = match question_str.and_then(|question_str| {
|
||||
base64::decode_config(question_str, base64::URL_SAFE_NO_PAD).ok()
|
||||
}) {
|
||||
let query = match question_str
|
||||
.and_then(|question_str| BASE64_URL_SAFE_NO_PAD.decode(question_str).ok())
|
||||
{
|
||||
Some(query) => query,
|
||||
_ => return None,
|
||||
};
|
||||
|
@ -185,23 +199,17 @@ impl DoH {
|
|||
}
|
||||
|
||||
async fn serve_odoh(&self, encrypted_query: Vec<u8>) -> Result<Response<Body>, http::Error> {
|
||||
let odoh_public_key = (*self.globals.odoh_rotator).clone().current_key();
|
||||
let (query, context) = match (*odoh_public_key)
|
||||
.clone()
|
||||
.decrypt_query(encrypted_query)
|
||||
.await
|
||||
{
|
||||
let odoh_public_key = (*self.globals.odoh_rotator).clone().current_public_key();
|
||||
let (query, context) = match (*odoh_public_key).clone().decrypt_query(encrypted_query) {
|
||||
Ok((q, context)) => (q.to_vec(), context),
|
||||
Err(e) => return http_error(StatusCode::from(e)),
|
||||
};
|
||||
|
||||
let resp = match self.proxy(query).await {
|
||||
Ok(resp) => resp,
|
||||
Err(e) => return http_error(StatusCode::from(e)),
|
||||
};
|
||||
|
||||
let encrypted_resp = match context.encrypt_response(resp.packet).await {
|
||||
Ok(resp) => self.build_response(resp, 0u32, DoHType::Oblivious.as_str()),
|
||||
let encrypted_resp = match context.encrypt_response(resp.packet) {
|
||||
Ok(resp) => self.build_response(resp, 0u32, DoHType::Oblivious.as_str(), false),
|
||||
Err(e) => return http_error(StatusCode::from(e)),
|
||||
};
|
||||
|
||||
|
@ -231,12 +239,13 @@ impl DoH {
|
|||
}
|
||||
|
||||
async fn serve_odoh_configs(&self) -> Result<Response<Body>, http::Error> {
|
||||
let odoh_public_key = (*self.globals.odoh_rotator).clone().current_key();
|
||||
let configs = (*odoh_public_key).clone().config();
|
||||
let odoh_public_key = (*self.globals.odoh_rotator).clone().current_public_key();
|
||||
let configs = (*odoh_public_key).clone().into_config();
|
||||
match self.build_response(
|
||||
configs,
|
||||
ODOH_KEY_ROTATION_SECS,
|
||||
"application/octet-stream".to_string(),
|
||||
true,
|
||||
) {
|
||||
Ok(resp) => Ok(resp),
|
||||
Err(e) => http_error(StatusCode::from(e)),
|
||||
|
@ -248,13 +257,10 @@ impl DoH {
|
|||
content_types: &[&'static str],
|
||||
) -> Option<&'static str> {
|
||||
let accept = headers.get(hyper::header::ACCEPT);
|
||||
let accept = match accept {
|
||||
None => return None,
|
||||
Some(accept) => accept,
|
||||
};
|
||||
for part in accept.to_str().unwrap_or("").split(",").map(|s| s.trim()) {
|
||||
let accept = accept?;
|
||||
for part in accept.to_str().unwrap_or("").split(',').map(|s| s.trim()) {
|
||||
if let Some(found) = part
|
||||
.split(";")
|
||||
.split(';')
|
||||
.next()
|
||||
.map(|s| s.trim().to_ascii_lowercase())
|
||||
{
|
||||
|
@ -309,7 +315,7 @@ impl DoH {
|
|||
.status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
return Err(response);
|
||||
Err(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -340,22 +346,65 @@ impl DoH {
|
|||
}
|
||||
let _ = dns::set_edns_max_payload_size(&mut query, MAX_DNS_RESPONSE_LEN as _);
|
||||
let globals = &self.globals;
|
||||
let socket = UdpSocket::bind(&globals.local_bind_address)
|
||||
.await
|
||||
.map_err(DoHError::Io)?;
|
||||
let expected_server_address = globals.server_address;
|
||||
let (min_ttl, max_ttl, err_ttl) = (globals.min_ttl, globals.max_ttl, globals.err_ttl);
|
||||
socket
|
||||
.send_to(&query, &globals.server_address)
|
||||
.map_err(DoHError::Io)
|
||||
.await?;
|
||||
let mut packet = vec![0; MAX_DNS_RESPONSE_LEN];
|
||||
let (len, response_server_address) =
|
||||
socket.recv_from(&mut packet).map_err(DoHError::Io).await?;
|
||||
if len < MIN_DNS_PACKET_LEN || expected_server_address != response_server_address {
|
||||
return Err(DoHError::UpstreamIssue);
|
||||
let (min_ttl, max_ttl, err_ttl) = (globals.min_ttl, globals.max_ttl, globals.err_ttl);
|
||||
|
||||
// UDP
|
||||
{
|
||||
let socket = UdpSocket::bind(&globals.local_bind_address)
|
||||
.await
|
||||
.map_err(DoHError::Io)?;
|
||||
let expected_server_address = globals.server_address;
|
||||
socket
|
||||
.send_to(&query, &globals.server_address)
|
||||
.map_err(DoHError::Io)
|
||||
.await?;
|
||||
let (len, response_server_address) =
|
||||
socket.recv_from(&mut packet).map_err(DoHError::Io).await?;
|
||||
if len < MIN_DNS_PACKET_LEN || expected_server_address != response_server_address {
|
||||
return Err(DoHError::UpstreamIssue);
|
||||
}
|
||||
packet.truncate(len);
|
||||
}
|
||||
packet.truncate(len);
|
||||
|
||||
// TCP
|
||||
if dns::is_truncated(&packet) {
|
||||
let clients_count = self.globals.clients_count.current();
|
||||
if self.globals.max_clients >= UDP_TCP_RATIO
|
||||
&& clients_count >= self.globals.max_clients / UDP_TCP_RATIO
|
||||
{
|
||||
return Err(DoHError::TooManyTcpSessions);
|
||||
}
|
||||
let socket = match globals.server_address {
|
||||
SocketAddr::V4(_) => TcpSocket::new_v4(),
|
||||
SocketAddr::V6(_) => TcpSocket::new_v6(),
|
||||
}
|
||||
.map_err(DoHError::Io)?;
|
||||
let mut ext_socket = socket
|
||||
.connect(globals.server_address)
|
||||
.await
|
||||
.map_err(DoHError::Io)?;
|
||||
ext_socket.set_nodelay(true).map_err(DoHError::Io)?;
|
||||
let mut binlen = [0u8, 0];
|
||||
BigEndian::write_u16(&mut binlen, query.len() as u16);
|
||||
ext_socket.write_all(&binlen).await.map_err(DoHError::Io)?;
|
||||
ext_socket.write_all(&query).await.map_err(DoHError::Io)?;
|
||||
ext_socket.flush().await.map_err(DoHError::Io)?;
|
||||
ext_socket
|
||||
.read_exact(&mut binlen)
|
||||
.await
|
||||
.map_err(DoHError::Io)?;
|
||||
let packet_len = BigEndian::read_u16(&binlen) as usize;
|
||||
if !(MIN_DNS_PACKET_LEN..=MAX_DNS_RESPONSE_LEN).contains(&packet_len) {
|
||||
return Err(DoHError::UpstreamIssue);
|
||||
}
|
||||
packet = vec![0u8; packet_len];
|
||||
ext_socket
|
||||
.read_exact(&mut packet)
|
||||
.await
|
||||
.map_err(DoHError::Io)?;
|
||||
}
|
||||
|
||||
let ttl = if dns::is_recoverable_error(&packet) {
|
||||
err_ttl
|
||||
} else {
|
||||
|
@ -375,21 +424,27 @@ impl DoH {
|
|||
packet: Vec<u8>,
|
||||
ttl: u32,
|
||||
content_type: String,
|
||||
cors: bool,
|
||||
) -> Result<Response<Body>, DoHError> {
|
||||
let packet_len = packet.len();
|
||||
let response = Response::builder()
|
||||
let mut response_builder = Response::builder()
|
||||
.header(hyper::header::CONTENT_LENGTH, packet_len)
|
||||
.header(hyper::header::CONTENT_TYPE, content_type.as_str())
|
||||
.header(
|
||||
hyper::header::CACHE_CONTROL,
|
||||
format!(
|
||||
"max-age={}, stale-if-error={}, stale-while-revalidate={}",
|
||||
ttl, STALE_IF_ERROR_SECS, STALE_WHILE_REVALIDATE_SECS
|
||||
"max-age={ttl}, stale-if-error={STALE_IF_ERROR_SECS}, \
|
||||
stale-while-revalidate={STALE_WHILE_REVALIDATE_SECS}"
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
);
|
||||
if cors {
|
||||
response_builder =
|
||||
response_builder.header(hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
}
|
||||
let response = response_builder
|
||||
.body(Body::from(packet))
|
||||
.unwrap();
|
||||
.map_err(|_| DoHError::InvalidData)?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
|
@ -446,9 +501,9 @@ impl DoH {
|
|||
self.globals.tls_cert_path.is_some() && self.globals.tls_cert_key_path.is_some();
|
||||
}
|
||||
if tls_enabled {
|
||||
println!("Listening on https://{}{}", listen_address, path);
|
||||
println!("Listening on https://{listen_address}{path}");
|
||||
} else {
|
||||
println!("Listening on http://{}{}", listen_address, path);
|
||||
println!("Listening on http://{listen_address}{path}");
|
||||
}
|
||||
|
||||
let mut server = Http::new();
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
use crate::constants::ODOH_KEY_ROTATION_SECS;
|
||||
use crate::errors::DoHError;
|
||||
use arc_swap::ArcSwap;
|
||||
use hpke::kex::Serializable;
|
||||
use odoh_rs::key_utils::derive_keypair_from_seed;
|
||||
use odoh_rs::protocol::{
|
||||
create_response_msg, parse_received_query, Deserialize, ObliviousDoHConfig,
|
||||
ObliviousDoHConfigContents, ObliviousDoHConfigs, ObliviousDoHKeyPair, ObliviousDoHMessage,
|
||||
ObliviousDoHMessageType, ObliviousDoHQueryBody, Serialize, RESPONSE_NONCE_SIZE,
|
||||
};
|
||||
use rand::Rng;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use odoh_rs::{
|
||||
Deserialize, ObliviousDoHConfig, ObliviousDoHConfigs, ObliviousDoHKeyPair, ObliviousDoHMessage,
|
||||
ObliviousDoHMessagePlaintext, OdohSecret, ResponseNonce, Serialize,
|
||||
};
|
||||
use rand::Rng;
|
||||
use tokio::runtime;
|
||||
|
||||
// https://cfrg.github.io/draft-irtf-cfrg-hpke/draft-irtf-cfrg-hpke.html#name-algorithm-identifiers
|
||||
const DEFAULT_HPKE_SEED_SIZE: usize = 32;
|
||||
const DEFAULT_HPKE_KEM: u16 = 0x0020; // DHKEM(X25519, HKDF-SHA256)
|
||||
const DEFAULT_HPKE_KDF: u16 = 0x0001; // KDF(SHA-256)
|
||||
const DEFAULT_HPKE_AEAD: u16 = 0x0001; // AEAD(AES-GCM-128)
|
||||
use crate::constants::ODOH_KEY_ROTATION_SECS;
|
||||
use crate::errors::DoHError;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ODoHPublicKey {
|
||||
key: ObliviousDoHKeyPair,
|
||||
key_pair: ObliviousDoHKeyPair,
|
||||
serialized_configs: Vec<u8>,
|
||||
}
|
||||
|
||||
|
@ -34,102 +27,70 @@ impl fmt::Debug for ODoHPublicKey {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ODoHQueryContext {
|
||||
query: ObliviousDoHQueryBody,
|
||||
secret: Vec<u8>,
|
||||
}
|
||||
|
||||
fn generate_key_pair() -> ObliviousDoHKeyPair {
|
||||
let ikm = rand::thread_rng().gen::<[u8; DEFAULT_HPKE_SEED_SIZE]>();
|
||||
let (secret_key, public_key) = derive_keypair_from_seed(&ikm);
|
||||
let public_key_bytes = public_key.to_bytes().to_vec();
|
||||
let odoh_public_key = ObliviousDoHConfigContents {
|
||||
kem_id: DEFAULT_HPKE_KEM,
|
||||
kdf_id: DEFAULT_HPKE_KDF,
|
||||
aead_id: DEFAULT_HPKE_AEAD,
|
||||
public_key: public_key_bytes,
|
||||
};
|
||||
ObliviousDoHKeyPair {
|
||||
private_key: secret_key,
|
||||
public_key: odoh_public_key,
|
||||
}
|
||||
query: ObliviousDoHMessagePlaintext,
|
||||
server_secret: OdohSecret,
|
||||
}
|
||||
|
||||
impl ODoHPublicKey {
|
||||
pub fn new() -> Result<ODoHPublicKey, DoHError> {
|
||||
let key_pair = generate_key_pair();
|
||||
let config = ObliviousDoHConfig::new(
|
||||
&key_pair
|
||||
.public_key
|
||||
.clone()
|
||||
.to_bytes()
|
||||
.map_err(|e| DoHError::ODoHConfigError(e))?,
|
||||
)
|
||||
.map_err(|e| DoHError::ODoHConfigError(e))?;
|
||||
|
||||
let serialized_configs = ObliviousDoHConfigs {
|
||||
configs: vec![config.clone()],
|
||||
}
|
||||
.to_bytes()
|
||||
.map_err(|e| DoHError::ODoHConfigError(e))?
|
||||
.to_vec();
|
||||
|
||||
let key_pair = ObliviousDoHKeyPair::new(&mut rand::thread_rng());
|
||||
let config = ObliviousDoHConfig::from(key_pair.public().clone());
|
||||
let mut serialized_configs = Vec::new();
|
||||
ObliviousDoHConfigs::from(vec![config])
|
||||
.serialize(&mut serialized_configs)
|
||||
.map_err(|e| DoHError::ODoHConfigError(e.into()))?;
|
||||
Ok(ODoHPublicKey {
|
||||
key: key_pair,
|
||||
serialized_configs: serialized_configs,
|
||||
key_pair,
|
||||
serialized_configs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn config(self) -> Vec<u8> {
|
||||
pub fn into_config(self) -> Vec<u8> {
|
||||
self.serialized_configs
|
||||
}
|
||||
|
||||
pub async fn decrypt_query(
|
||||
pub fn decrypt_query(
|
||||
self,
|
||||
encrypted_query: Vec<u8>,
|
||||
) -> Result<(Vec<u8>, ODoHQueryContext), DoHError> {
|
||||
let odoh_query = match ObliviousDoHMessage::from_bytes(&encrypted_query) {
|
||||
Ok(q) => {
|
||||
if q.msg_type != ObliviousDoHMessageType::Query {
|
||||
return Err(DoHError::InvalidData);
|
||||
}
|
||||
q
|
||||
}
|
||||
Err(_) => return Err(DoHError::InvalidData),
|
||||
};
|
||||
|
||||
match self.key.public_key.identifier() {
|
||||
let odoh_query = ObliviousDoHMessage::deserialize(&mut bytes::Bytes::from(encrypted_query))
|
||||
.map_err(|_| DoHError::InvalidData)?;
|
||||
match self.key_pair.public().identifier() {
|
||||
Ok(key_id) => {
|
||||
if !key_id.eq(&odoh_query.key_id) {
|
||||
if !key_id.eq(&odoh_query.key_id()) {
|
||||
return Err(DoHError::StaleKey);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(DoHError::InvalidData),
|
||||
};
|
||||
|
||||
let (query, server_secret) = match parse_received_query(&self.key, &encrypted_query).await {
|
||||
let (query, server_secret) = match odoh_rs::decrypt_query(&odoh_query, &self.key_pair) {
|
||||
Ok((pq, ss)) => (pq, ss),
|
||||
Err(_) => return Err(DoHError::InvalidData),
|
||||
};
|
||||
let context = ODoHQueryContext {
|
||||
query: query.clone(),
|
||||
secret: server_secret,
|
||||
server_secret,
|
||||
};
|
||||
Ok((query.dns_msg.clone(), context))
|
||||
Ok((query.into_msg().to_vec(), context))
|
||||
}
|
||||
}
|
||||
|
||||
impl ODoHQueryContext {
|
||||
pub async fn encrypt_response(self, response_body: Vec<u8>) -> Result<Vec<u8>, DoHError> {
|
||||
let response_nonce = rand::thread_rng().gen::<[u8; RESPONSE_NONCE_SIZE]>();
|
||||
create_response_msg(
|
||||
&self.secret,
|
||||
&response_body,
|
||||
None,
|
||||
Some(response_nonce.to_vec()),
|
||||
pub fn encrypt_response(self, response_body: Vec<u8>) -> Result<Vec<u8>, DoHError> {
|
||||
let response_nonce = rand::thread_rng().r#gen::<ResponseNonce>();
|
||||
let response_body_ = ObliviousDoHMessagePlaintext::new(response_body, 0);
|
||||
let encrypted_response = odoh_rs::encrypt_response(
|
||||
&self.query,
|
||||
&response_body_,
|
||||
self.server_secret,
|
||||
response_nonce,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| DoHError::InvalidData)
|
||||
.map_err(|_| DoHError::InvalidData)?;
|
||||
let mut encrypted_response_bytes = Vec::new();
|
||||
encrypted_response
|
||||
.serialize(&mut encrypted_response_bytes)
|
||||
.map_err(|_| DoHError::InvalidData)?;
|
||||
Ok(encrypted_response_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,31 +101,31 @@ pub struct ODoHRotator {
|
|||
|
||||
impl ODoHRotator {
|
||||
pub fn new(runtime_handle: runtime::Handle) -> Result<ODoHRotator, DoHError> {
|
||||
let odoh_key = match ODoHPublicKey::new() {
|
||||
let public_key = match ODoHPublicKey::new() {
|
||||
Ok(key) => Arc::new(ArcSwap::from_pointee(key)),
|
||||
Err(e) => panic!("ODoH key rotation error: {}", e),
|
||||
};
|
||||
|
||||
let current_key = Arc::clone(&odoh_key);
|
||||
let current_key = Arc::clone(&public_key);
|
||||
|
||||
runtime_handle.clone().spawn(async move {
|
||||
runtime_handle.spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(ODOH_KEY_ROTATION_SECS.into())).await;
|
||||
match ODoHPublicKey::new() {
|
||||
Ok(key) => {
|
||||
current_key.store(Arc::new(key));
|
||||
}
|
||||
Err(e) => eprintln!("ODoH key rotation error: {}", e),
|
||||
Err(e) => eprintln!("ODoH key rotation error: {e}"),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Ok(ODoHRotator {
|
||||
key: Arc::clone(&odoh_key),
|
||||
key: Arc::clone(&public_key),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn current_key(&self) -> Arc<ODoHPublicKey> {
|
||||
pub fn current_public_key(&self) -> Arc<ODoHPublicKey> {
|
||||
let key = Arc::clone(&self.key);
|
||||
Arc::clone(&key.load())
|
||||
}
|
||||
|
|
|
@ -1,48 +1,48 @@
|
|||
use crate::constants::CERTS_WATCH_DELAY_SECS;
|
||||
use crate::errors::*;
|
||||
use crate::{DoH, LocalExecutor};
|
||||
|
||||
use futures::{future::FutureExt, join, select};
|
||||
use hyper::server::conn::Http;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, Cursor, Read};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::{future::FutureExt, join, select};
|
||||
use hyper::server::conn::Http;
|
||||
use tokio::{
|
||||
net::TcpListener,
|
||||
sync::mpsc::{self, Receiver},
|
||||
};
|
||||
use tokio_rustls::{
|
||||
rustls::{internal::pemfile, NoClientAuth, ServerConfig},
|
||||
rustls::{Certificate, PrivateKey, ServerConfig},
|
||||
TlsAcceptor,
|
||||
};
|
||||
|
||||
use crate::constants::CERTS_WATCH_DELAY_SECS;
|
||||
use crate::errors::*;
|
||||
use crate::{DoH, LocalExecutor};
|
||||
|
||||
pub fn create_tls_acceptor<P, P2>(certs_path: P, certs_keys_path: P2) -> io::Result<TlsAcceptor>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
P2: AsRef<Path>,
|
||||
{
|
||||
let certs = {
|
||||
let certs: Vec<_> = {
|
||||
let certs_path_str = certs_path.as_ref().display().to_string();
|
||||
let mut reader = BufReader::new(File::open(certs_path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!(
|
||||
"Unable to load the certificates [{}]: {}",
|
||||
certs_path_str,
|
||||
e.to_string()
|
||||
),
|
||||
format!("Unable to load the certificates [{certs_path_str}]: {e}"),
|
||||
)
|
||||
})?);
|
||||
pemfile::certs(&mut reader).map_err(|_| {
|
||||
rustls_pemfile::certs(&mut reader).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unable to parse the certificates",
|
||||
)
|
||||
})?
|
||||
};
|
||||
let certs_keys = {
|
||||
}
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
let certs_keys: Vec<_> = {
|
||||
let certs_keys_path_str = certs_keys_path.as_ref().display().to_string();
|
||||
let encoded_keys = {
|
||||
let mut encoded_keys = vec![];
|
||||
|
@ -50,25 +50,21 @@ where
|
|||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!(
|
||||
"Unable to load the certificate keys [{}]: {}",
|
||||
certs_keys_path_str,
|
||||
e.to_string()
|
||||
),
|
||||
format!("Unable to load the certificate keys [{certs_keys_path_str}]: {e}"),
|
||||
)
|
||||
})?
|
||||
.read_to_end(&mut encoded_keys)?;
|
||||
encoded_keys
|
||||
};
|
||||
let mut reader = Cursor::new(encoded_keys);
|
||||
let pkcs8_keys = pemfile::pkcs8_private_keys(&mut reader).map_err(|_| {
|
||||
let pkcs8_keys = rustls_pemfile::pkcs8_private_keys(&mut reader).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unable to parse the certificates private keys (PKCS8)",
|
||||
)
|
||||
})?;
|
||||
reader.set_position(0);
|
||||
let mut rsa_keys = pemfile::rsa_private_keys(&mut reader).map_err(|_| {
|
||||
let mut rsa_keys = rustls_pemfile::rsa_private_keys(&mut reader).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unable to parse the certificates private keys (RSA)",
|
||||
|
@ -82,21 +78,27 @@ where
|
|||
"No private keys found - Make sure that they are in PKCS#8/PEM format",
|
||||
));
|
||||
}
|
||||
keys
|
||||
keys.drain(..).map(PrivateKey).collect()
|
||||
};
|
||||
let mut server_config = ServerConfig::new(NoClientAuth::new());
|
||||
server_config.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]);
|
||||
let has_valid_cert_and_key = certs_keys.into_iter().any(|certs_key| {
|
||||
server_config
|
||||
.set_single_cert(certs.clone(), certs_key)
|
||||
.is_ok()
|
||||
});
|
||||
if !has_valid_cert_and_key {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Invalid private key for the given certificate",
|
||||
));
|
||||
}
|
||||
|
||||
let mut server_config = certs_keys
|
||||
.into_iter()
|
||||
.find_map(|certs_key| {
|
||||
let server_config_builder = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth();
|
||||
match server_config_builder.with_single_cert(certs.clone(), certs_key) {
|
||||
Ok(found_config) => Some(found_config),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unable to find a valid certificate and key",
|
||||
)
|
||||
})?;
|
||||
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
Ok(TlsAcceptor::from(Arc::new(server_config)))
|
||||
}
|
||||
|
||||
|
@ -152,12 +154,12 @@ impl DoH {
|
|||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("TLS certificates error: {}", e),
|
||||
Err(e) => eprintln!("TLS certificates error: {e}"),
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(CERTS_WATCH_DELAY_SECS.into())).await;
|
||||
}
|
||||
Ok::<_, DoHError>(())
|
||||
};
|
||||
return join!(https_service, cert_service).0;
|
||||
join!(https_service, cert_service).0
|
||||
}
|
||||
}
|
||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -1,5 +1,5 @@
|
|||
#[global_allocator]
|
||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
@ -8,17 +8,17 @@ mod config;
|
|||
mod constants;
|
||||
mod utils;
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use libdoh::odoh::ODoHRotator;
|
||||
use libdoh::reexports::tokio;
|
||||
use libdoh::*;
|
||||
|
||||
use crate::config::*;
|
||||
use crate::constants::*;
|
||||
|
||||
use libdoh::odoh::ODoHRotator;
|
||||
use libdoh::reexports::tokio;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let mut runtime_builder = tokio::runtime::Builder::new_multi_thread();
|
||||
runtime_builder.enable_all();
|
||||
|
|
16
src/utils.rs
16
src/utils.rs
|
@ -2,25 +2,23 @@
|
|||
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
|
||||
pub(crate) fn verify_sock_addr(arg_val: String) -> Result<(), String> {
|
||||
pub(crate) fn verify_sock_addr(arg_val: &str) -> Result<String, String> {
|
||||
match arg_val.parse::<SocketAddr>() {
|
||||
Ok(_addr) => Ok(()),
|
||||
Ok(_addr) => Ok(arg_val.to_string()),
|
||||
Err(_) => Err(format!(
|
||||
"Could not parse \"{}\" as a valid socket address (with port).",
|
||||
arg_val
|
||||
"Could not parse \"{arg_val}\" as a valid socket address (with port)."
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verify_remote_server(arg_val: String) -> Result<(), String> {
|
||||
pub(crate) fn verify_remote_server(arg_val: &str) -> Result<String, String> {
|
||||
match arg_val.to_socket_addrs() {
|
||||
Ok(mut addr_iter) => match addr_iter.next() {
|
||||
Some(_) => Ok(()),
|
||||
Some(_) => Ok(arg_val.to_string()),
|
||||
None => Err(format!(
|
||||
"Could not parse \"{}\" as a valid remote uri",
|
||||
arg_val
|
||||
"Could not parse \"{arg_val}\" as a valid remote uri"
|
||||
)),
|
||||
},
|
||||
Err(err) => Err(format!("{}", err)),
|
||||
Err(err) => Err(format!("{err}")),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue