Compare commits

..

153 Commits

Author SHA1 Message Date
Star Brilliant
8bc06acc6e Move StartLimitIntervalSec=0 from [Service] to [Unit]
This solves the warning message:
> systemd[1]: /usr/lib/systemd/system/doh-client.service:16: Unknown key 'StartLimitIntervalSec' in section [Service], ignoring.
2025-05-24 12:45:26 +00:00
Star Brilliant
0263a32c22 Merge pull request #173 from vinnyperella/patch-1 2025-05-18 00:10:28 +00:00
Vinny
354d0377b3 chore: upgrade dependencies 2025-05-14 19:26:12 +00:00
Star Brilliant
59a47c881b Merge pull request #171 from m13253/m13253/restart-backoff
When systemd service fail to start, use an exponential backoff delay to restart it
2025-05-13 01:50:24 +00:00
Star Brilliant
03da3a801f When systemd service fail to start, use an exponential backoff delay to restart it
This solves an issue that on (at least) Fedora and if NetworkManager starts too slow, systemd may stop trying to start it.
2025-05-09 00:12:11 +00:00
GreyXor
f13dea391f chore: v2.3.10 2025-03-29 01:02:43 +01:00
GreyXor
45edaad516 chore: upgrade dependencies 2025-03-29 01:00:16 +01:00
Star Brilliant
2179ee0054 Merge pull request #169 from vinnyperella/patch-4
Update go.yml
2025-03-28 14:07:47 +00:00
Vinny
6ddb1ad401 Update go.yml
Updated actions checkout/cache to v4.
2025-03-28 14:04:53 +00:00
GreyXor
35e0835949 chore: v2.3.9 2025-03-28 12:20:42 +01:00
GreyXor
5c744889be chore: upgrade dependencies 2025-03-28 11:47:07 +01:00
GreyXor
cb1c336217 chore: upgrade dependencies 2025-02-20 10:41:30 +01:00
GreyXor
f82ac1118e feat: new version 2.3.8 2025-01-28 09:09:13 +01:00
GreyXor
e5fef5690e chore: upgrade package dependencies 2025-01-28 09:07:28 +01:00
GreyXor
9c997f1491 Merge pull request #166 from vinnyperella/patch-3
chore: update dependencies
2025-01-27 22:51:34 +01:00
Vinny
d33fc60182 chore: update dependencies 2025-01-27 15:01:08 -05:00
Vinny
89bb7e95a6 chore: update dependencies 2025-01-27 14:58:41 -05:00
GreyXor
c5a06988d5 chore: upgrade dependencies 2024-12-28 18:17:23 +01:00
Satish Gaikwad
ea57996685 Merge pull request #162 from m13253/add-multi-upstream-dns-servers 2024-12-20 08:41:18 -08:00
Satish Gaikwad
0a1aa98a01 Updated comment 2024-12-19 22:27:26 +00:00
Satish Gaikwad
7ca84d162f Updated comment 2024-12-19 22:24:23 +00:00
Satish Gaikwad
0ea5c5015f Added a note for multiple DNS server support in the container image 2024-12-17 18:56:10 +00:00
Satish Gaikwad
65424da23f Update example to use multiple upstream DNS servers
Update example to use multiple upstream DNS servers
2024-12-17 10:41:51 -08:00
GreyXor
d35e6a6117 chore: upgrade package dependencies 2024-11-14 12:47:00 +01:00
Star Brilliant
4a98f0997b Update to actions/upload-artifact@v4 2024-11-13 18:58:47 +00:00
GreyXor
b70babf43e chore: upgrade package dependencies 2024-11-09 09:00:22 +01:00
GreyXor
967c43016c chore: upgrade package dependencies 2024-08-10 08:39:30 +02:00
GreyXor
a10da0ed10 Pre-bump to next version 2.3.7 2024-07-05 18:14:09 +02:00
GreyXor
379aa917f9 chore: upgrade package dependencies 2024-07-05 18:10:37 +02:00
GreyXor
1a90868fe9 Merge pull request #159 from testwill/fmt
chore: unnecessary use of fmt.Sprintf
2024-06-06 15:32:25 +02:00
guoguangwu
fa6e999d54 chore: unnecessary use of fmt.Sprintf
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
2024-05-10 17:18:49 +08:00
GreyXor
72be4857a3 chore: upgrade package dependencies 2024-05-06 16:18:48 +02:00
GreyXor
a5e8a7e089 chore: upgrade package dependencies 2024-04-11 11:56:15 +02:00
Satish Gaikwad
6b7f3927db Merge pull request #157 from m13253/Pre-bump-doh-client-to-next-version-2-3-6
Pre-bump doh-client to next version 2.3.6
2024-03-12 13:14:16 -07:00
Satish Gaikwad
2e2b012c9d Pre-bump to next version 2.3.6 2024-03-12 13:13:03 -07:00
Satish Gaikwad
9e67d1b8bd Merge pull request #156 from m13253/pre-bump-next-version-to-2-3-6
Pre-bump to next version 2.3.6
2024-03-12 13:09:49 -07:00
Satish Gaikwad
b8c3122f54 Pre-bump to next version 2.3.6 2024-03-12 13:07:28 -07:00
GreyXor
65622feb5f chore: upgrade package dependencies 2024-03-05 11:29:59 +01:00
GreyXor
057797fed3 chore: upgrade package dependencies 2024-02-08 11:58:30 +01:00
GreyXor
e171ec0da2 chore: upgrade package dependencies 2024-01-08 21:19:31 +01:00
GreyXor
a2b984f816 chore: upgrade package dependencies 2023-12-21 14:41:57 +01:00
GreyXor
2662d9f35b chores: upgrade deps 2023-11-13 12:40:37 +01:00
GreyXor
7d9ea18607 upgrade dependencies 2023-10-12 16:31:32 +02:00
GreyXor
967ca0103e upgrade dependencies 2023-10-06 10:58:27 +02:00
GreyXor
6fb0df257e upgrade dependencies 2023-09-15 14:25:53 +02:00
Jamesits
d00be8b698 Merge pull request #152 from Gontier-Julien/ldflags
Add ldflags
2023-08-24 08:43:38 -07:00
Gontier Julien
f08eb16d0b Add ldflags
Signed-off-by: Gontier Julien <gontierjulien68@gmail.com>
2023-08-24 17:34:20 +02:00
James Swineson
2bda149686 revert last commit 2023-08-23 10:38:03 +08:00
James Swineson
299e8f9245 CI: parallelize multiarch build 2023-08-23 10:32:22 +08:00
James Swineson
f3e529865a enable multiarch Docker builds 2023-08-23 10:20:08 +08:00
James Swineson
94bc8c0587 remove unused logout action 2023-08-23 10:14:03 +08:00
James Swineson
f5f03513f8 fix CI config grammar 2023-08-23 10:12:52 +08:00
James Swineson
9e1f2c70cc update environment tag 2023-08-23 10:11:12 +08:00
James Swineson
979780c8d5 add CI job for Docker Hub build and push 2023-08-23 10:08:23 +08:00
James Swineson
f697ba1e1e bump Golang version in CI to 1.21 2023-08-23 10:06:23 +08:00
GreyXor
32f3ad71f7 upgrade dependencies 2023-07-06 10:08:37 +02:00
GreyXor
83924dd76b Fix rand usage, typos and 30 bytes saved by fields alignment 2023-07-04 16:11:18 +02:00
GreyXor
e72363306b cleanup code style 2023-07-04 16:09:12 +02:00
GreyXor
e4d309bbc0 upgrade dependencies 2023-07-04 16:06:26 +02:00
GreyXor
0690c41610 Upgrade dependencies 2023-05-25 20:44:19 +02:00
GreyXor
244e4b5b8f Upgrade dependencies 2023-03-12 12:39:53 +01:00
Star Brilliant
a7708f71b3 Pre-bump to next version 2.3.4 2023-01-26 10:13:43 +00:00
Star Brilliant
d6a5c91f67 Release 2.3.3 2023-01-26 10:13:10 +00:00
Star Brilliant
fdc1b81e42 Properly truncate DNS packets
This should fix issue #144.
2023-01-25 06:47:13 +00:00
GreyXor
70fc8578c7 feat: upgrade deps 2023-01-11 09:33:57 +01:00
GreyXor
94c223df4b feat: upgrade deps 2022-12-16 13:52:17 +01:00
GreyXor
b9bf6e80f4 fix: io and os instead of deprecated ioutil
Deprecated: As of Go 1.16, the same functionality is now provided by package io or package os, and those implementations should be preferred in new code. See the specific function documentation for details.
https://pkg.go.dev/io/ioutil
2022-10-29 11:35:22 +02:00
GreyXor
1b7eed5afe chore: upgrade deps 2022-10-29 11:19:04 +02:00
Star Brilliant
393019fc14 Merge pull request #140 from m13253/fix-dynamic-user 2022-10-07 16:24:08 +00:00
Star Brilliant
604daa663f systemd: Use DynamicUser=yes instead of User=nobody
Fix issue #139
2022-10-06 16:07:19 +00:00
Star Brilliant
8bedfc4bab Pre-bump to next version 2.3.3 2022-09-19 00:31:26 +00:00
Star Brilliant
ae0333f1c2 Release 2.3.2 2022-09-19 00:30:59 +00:00
Star Brilliant
34014d847e Merge pull request #137 from Gontier-Julien/patch
Removing Firefox 61-62 workaround patch
2022-09-15 06:01:44 +00:00
Star Brilliant
71eecf7b8a Merge pull request #133 from m13253/docs/DoT
docs: explain how to use DNS-over-TLS with nginx/STunnel
2022-09-15 06:01:26 +00:00
Star Brilliant
6e74bbd061 Merge pull request #132 from m13253/docs/README-updates
docs: some README additions
2022-09-15 06:00:16 +00:00
Star Brilliant
f212286c4f Merge pull request #134 from m13253/fix/parse-ecs-golang 2022-09-14 15:23:42 +00:00
Gontier Julien
1fff629074 Removing Firefox 61-62 patch
Signed-off-by: Gontier Julien <gontierjulien68@gmail.com>
2022-09-14 11:15:23 +02:00
Star Brilliant
533ea58e67 Merge pull request #135 from m13253/upgrade-deps
chore: upgrade deps
2022-09-11 07:59:12 +00:00
GreyXor
b7b935767f chore: upgrade deps 2022-09-09 11:56:43 +02:00
gdm85
f1b3133982 fix: add unit tests for CIDR subnets parsing 2022-09-03 13:58:56 +02:00
gdm85
a519b5a9c4 docs: explain how to use DNS-over-TLS with nginx/STunnel 2022-09-03 10:54:06 +02:00
gdm85
80e95cd028 docs: mention poor compatibility with dnscrypt-proxy 2022-09-03 10:32:29 +02:00
gdm85
1d59772fad docs: mention where to find logs 2022-09-03 10:31:14 +02:00
Star Brilliant
a375bea95d Merge pull request #131 from GreyXor/master
chore: upgrade deps
2022-08-17 16:07:35 +00:00
GreyXor
b98b01cc4e chore: upgrade deps 2022-08-17 10:32:42 +02:00
Star Brilliant
6276bed46f Pre-bump version to 2.3.2 2022-06-01 02:57:07 +00:00
Star Brilliant
19737361ad Release 2.3.1 2022-06-01 02:56:35 +00:00
Star Brilliant
791d2d43dd Merge pull request #128 from GreyXor/master 2022-05-31 11:31:46 +00:00
GreyXor
90753c3910 ci: update setup go version 2022-05-31 12:07:30 +02:00
GreyXor
221240a840 chore: update dependencies 2022-05-31 12:00:18 +02:00
StarBrilliant
b338c7ae52 Bump to version 2.3.1 2021-09-13 10:31:04 +00:00
StarBrilliant
9fd69439c4 Release 2.3.0 2021-09-13 10:30:38 +00:00
Star Brilliant
10eb8f5c87 Merge pull request #116 from leiless/gh-115-go-import-v2
GH#115: Fix Go module semver import
2021-09-13 05:27:45 +00:00
Fishbone
8cd4c4205d gh-115-go-import-v2: Suffix Go module path with /v2 2021-09-12 17:12:39 +08:00
Fishbone
63c6c1de91 gh-115-go-import-v2: Run go mod tidy 2021-09-12 17:09:53 +08:00
Star Brilliant
f25e9a706d Merge pull request #112 from 1574242600/patch-1
Fix not working example docker command
2021-07-15 19:04:41 -04:00
Nworm
a2e3b0cd4b readme: fix not working example docker command 2021-07-15 22:40:57 +08:00
Star Brilliant
f172a7b7fb Merge pull request #110 from gdm85/fix/simplify
Simplify doDNSQuery call
2021-05-15 12:16:35 +00:00
Star Brilliant
56a01679ad Merge pull request #108 from gdm85/fix/add-gh-action
Add GitHub actions to build master and each PR
2021-05-14 16:39:28 +00:00
Star Brilliant
05c3b1676d Merge pull request #107 from gdm85/fix/verbose-logging
Log for response patching only when verbose is enabled
2021-05-13 21:17:46 +00:00
gdm85
5af0d538ca Remove deps download, modern Go does it automatically 2021-05-13 19:20:46 +02:00
gdm85
0bbd26c1b5 Use Go 1.13 2021-05-13 19:19:44 +02:00
gdm85
8a13f085a6 Simplify doDNSQuery call 2021-05-13 19:15:54 +02:00
gdm85
849bc584cc Add GitHub actions to build master and each PR 2021-05-13 19:00:58 +02:00
gdm85
5f8371817b Log for response patching only when verbose is enabled 2021-05-13 18:54:09 +02:00
Star Brilliant
2e36b4ebcd New minimum Go version 2021-04-02 22:23:50 +00:00
Star Brilliant
02dbd9d954 Bump to version 2.2.6 2021-03-25 14:31:03 +00:00
Star Brilliant
0a76416f8e Release 2.2.5 2021-03-25 14:30:44 +00:00
Star Brilliant
82c50163c1 Merge pull request #99 from amincheloh/patch-1
Fix not working example docker command
2021-01-26 19:24:34 +08:00
Amin Cheloh
d5c1c592f6 Fix not working example docker command 2021-01-25 14:30:26 +07:00
Alex Chauvin
1cf98e87c9 add client certificate authentication (#98)
* add client certificate authentication
* fix #97 for ECS forward local addresses
2021-01-08 08:34:25 +00:00
Satish Gaikwad
e7461f2d85 Documentation update: Docker example update (#96)
* Set traefik container version to 2.3 in docker-compose example. This supports recent lets encrypt changes.
* Remove docker swarm related references. Docker swarm example is no more valid. Docker compose is the best example available atm.
2020-12-27 13:25:25 +00:00
Star Brilliant
608394e2d2 Bump to version 2.2.5 2020-12-06 22:53:33 +00:00
Star Brilliant
eb166ececa Release 2.2.4 2020-12-06 22:52:35 +00:00
Star Brilliant
f557e4aa29 Reformat the code 2020-11-24 12:38:16 +00:00
Alex Chauvin
967faec56c add options for ECS full subnet mask in server & TLS verification bypass in client (#92)
* add ECS full size & limit filtering

* add tls certification bypass in configuration

* flush log lines

* changes following pull request comments

* with fmt and reorg of libs in client.go
2020-11-24 12:35:23 +00:00
Star Brilliant
2aa7370aaf Bump to version 2.2.4 2020-11-22 13:27:30 +00:00
Star Brilliant
b63e86bab3 Release 2.2.3 2020-11-22 13:26:41 +00:00
Star Brilliant
7c96cd4436 Merge pull request #91 from dwoffinden/patch-1
Fix an inconsistency in the example doh-client.conf
2020-11-22 13:17:44 +00:00
Daniel Woffinden
f5f1a8f3f4 Fix an inconsistency in the example doh-client.conf
Above, it was said that 8.8.8.8 had good ECS, so don't contradict that further down.

This confused a reviewer of https://github.com/NixOS/nixpkgs/pull/104530 :)
2020-11-22 12:18:12 +00:00
Star Brilliant
4f46b89feb Resolve (some) linter warnings 2020-08-02 05:58:24 +08:00
Star Brilliant
2c7e70466e Rewrite globalip_test 2020-08-02 05:53:21 +08:00
Star Brilliant
88f9ef84d1 Merge pull request #83 from sanyo0714/globalip_use_iptree
Use ipTree to determine the global IP
2020-08-02 05:02:15 +08:00
Star Brilliant
63bceea638 Merge branch 'master' into globalip_use_iptree 2020-08-02 05:01:56 +08:00
Star Brilliant
16120fdc11 Bump to version 2.2.3 2020-08-02 04:44:31 +08:00
Star Brilliant
b2fcfb706c Release 2.2.2 2020-08-02 04:43:22 +08:00
Star Brilliant
64e9375e3b Merge pull request #85 from leiless/alidns-json-Question
json-dns/response.go: Fix variant question response in Response.Question
2020-08-02 02:46:52 +08:00
leixiang
34feec9f5d json-dns/response.go: Fix variant question response in Response.Question
Known affected DoH server:
    https://www.alidns.com/faqs/#dns-safe
2020-08-01 13:26:35 +08:00
Star Brilliant
6d30a12d5f Pass X-Real-IP to handlers.CombinedLoggingHandler
Note that X-Forwarded-For or guessed client IP are not used due to security concerns.
This should fix issue #71.
2020-07-30 20:44:18 +08:00
sanyo
0c878a6ad7 change git ignore 2020-07-29 10:50:38 +08:00
Star Brilliant
a8aed7e09a Use ExchangeClient for DNS request 2020-07-26 22:09:24 +08:00
sanyo
31ea7c520d Use ipTree to determine the global IP 2020-07-16 17:11:34 +08:00
Star Brilliant
6e99d8153a Merge pull request #78 from leiless/DNSSEC-OK
[JSON-DOH] Honor DNSSEC OK flag for incoming DNS requests
2020-04-19 14:49:55 +08:00
Star Brilliant
2d4495a0dd Merge pull request #79 from leiless/json-fix-empty-rr-names
[JSON-DOH] Fix DNS response empty []RR.Name
2020-04-19 14:49:07 +08:00
leixiang
b30056a590 doh-client/google.go: [JSON-DOH] Fix DNS response empty []RR.Name
Cloudflare JSON DOH may return empty RR names if r.Question[0].Name is "."
Which causes malformed DNS response
2020-04-19 11:28:28 +08:00
leixiang
b92da52539 .gitignore: Ignore make output binaries 2020-04-19 11:02:16 +08:00
leixiang
f43d2c69e0 doh-client/google.go: [JSON-DOH] Honor DNSSEC OK flag for incoming DNS requests 2020-04-19 10:57:03 +08:00
Star Brilliant
5f1f418664 Merge pull request #77 from Henrocker/patch-1
Upgrade Caddyfile config to v2 and drastically reduce size.
2020-04-10 23:30:01 +08:00
Henrik
9a316a56a8 Upgrade Caddyfile config to v2 and drastically reduce size.
Since Caddy v2 is in RC state, config should be updates to v2. Also config has been simplified a lot.

Running example with this config: https://dns.hnrk.io/dns

Cheers and happy easter 😊!
2020-04-10 17:05:37 +02:00
Star Brilliant
81b977ca11 Merge pull request #76 from satishweb/master
Docker compose based doh-server setup example
2020-04-05 20:42:36 +08:00
Satish Gaikwad
b7d252de7b Added arch types 2020-04-05 02:45:01 -07:00
Satish Gaikwad
e12b87b48d Enhanced documentation. Added Docker compose based doh-server deployment example. 2020-04-05 02:33:20 -07:00
Star Brilliant
09bdfe2b14 Merge pull request #75 from buckaroogeek/pihole
Added pi-hole as potential dnssec validator
2020-04-04 15:21:35 +08:00
Bradley G Smith
a84b65dd56 expose container to port 53/upd 2020-04-03 10:58:57 -07:00
Bradley G Smith
540f6e3043 Added pi-hole as potential dnssec validator 2020-04-03 08:07:34 -07:00
Star Brilliant
7db67db247 Merge pull request #74 from satishweb/master
Added example configuration for Docker Flow Proxy + Docker
2020-04-03 10:49:39 +08:00
Satish Gaikwad
026d89ac8d Added info on ipv6 support. Added simple docker run example in installation section 2020-04-02 10:42:33 -07:00
Satish Gaikwad
8228ea6299 Added example configuration for Docker Flow Proxy + Docker 2020-04-01 14:54:09 -07:00
Star Brilliant
59f79fb666 Merge pull request #67 from monperrus/patch-1
doc: document upstream_selector
2020-02-05 00:41:24 +08:00
Martin Monperrus
502caabd15 doc: document upstream_selector
fix #66
2020-02-04 15:39:29 +00:00
Star Brilliant
f151c90e9d Merge pull request #61 from m13253/feature/no_ecs_arg
Allow client opt-out of EDNS0 Client Subnet
2019-11-11 12:21:41 +08:00
James Swineson
d8e3969640 add no_ecs argument in query string support 2019-11-11 10:11:25 +08:00
Star Brilliant
475ef65f57 Bump version to 2.2.2 2019-10-29 06:07:30 +08:00
37 changed files with 1054 additions and 462 deletions

36
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: "Docker"
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
environment: "Docker Hub"
strategy:
matrix:
variant: ["client", "server"]
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
logout: true
- # TODO: port https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
name: Build and push
uses: docker/build-push-action@v4
with:
file: ./Dockerfile.${{ matrix.variant }}
tags: m13253/dns-over-https-${{ matrix.variant }}:latest
push: true
platforms: "linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8"

37
.github/workflows/go.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Go build for Linux
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.24.3
id: go
- name: Check out repository
uses: actions/checkout@v4
- name: Linux build
run: |
make
- name: Upload Linux build
uses: actions/upload-artifact@v4
with:
name: linux-amd64
path: |
doh-client/doh-client
doh-server/doh-server
- name: Cache
uses: actions/cache@v4
with:
# A directory to store and save the cache
path: ~/go/pkg/mod
# An explicit key for restoring and saving the cache
key: ${{ runner.os }}-${{ hashFiles('**/go.sum') }}

4
.gitignore vendored
View File

@@ -3,6 +3,9 @@
*.dll
*.so
*.dylib
darwin-wrapper/doh-logger
doh-client/doh-client
doh-server/doh-server
# Test binary, build with `go test -c`
*.test
@@ -14,3 +17,4 @@
.glide/
.idea/
vendor/

36
.golangci.yml Normal file
View File

@@ -0,0 +1,36 @@
---
issues:
fix: true
linters:
enable-all: true
disable:
- importas
- depguard
- lll
- exhaustruct
- perfsprint
- gochecknoinits
- wsl
- exportloopref
linters-settings:
revive:
enable-all-rules: true
rules:
- name: line-length-limit
disabled: true
gocritic:
enabled-tags:
- diagnostic
- style
- performance
- experimental
- opinionated
govet:
enable-all: true
gci:
sections:
- standard
- default
- prefix(github.com/m13253/dns-over-https/v2)
gofumpt:
extra-rules: true

View File

@@ -4,6 +4,53 @@ This Changelog records major changes between versions.
Not all changes are recorded. Please check git log for details.
## Version 2.3.3
- systemd: Use DynamicUser=yes instead of User=nobody (Fixed #139)
- Migrate deprecated Go packages `ioutil` to `io` and `os`
- Fix a bug that truncates the response improperly, causing malformed DNS responsed (Fixed #144)
## Version 2.3.2
- Documentation updates, including deploying recommenation alongside DoT, thanks @gdm85
- Add unit tests for CIDR subnets parsing, thanks @gdm85
- Removing Firefox 61-62 patch
Since this version, @gdm85, @GreyXor, @Jamesits will be able to maintain this repository alongside @m13253. Anyone who contributed to this project can also apply to be a maintainer.
This is because changes in life have delayed the development of this project. By constructing a community hopefully can we restore the pace of development.
## Version 2.3.1
- No new features in this release
- Bumped versions of Go toolchain and third-party dependencies, requested by #128
## Version 2.3.0
- The repository now conforms to the Go semvar standard (Fixed #115, thanks to @leiless)
## Version 2.2.5
- Add client certificate authentication
- Fixing documentation related to Docker
## Version 2.2.4
- Add options to configure ECS netmask length
- Add an option to disable TLS verification (Note: dangerous)
## Version 2.2.3
- Use the library ipTree to determine whether an IP is global routable, improving the performance
- Google's 8.8.8.8 resolver is now marked as "Good ECS" in the example configuration file
## Version 2.2.2
- Allow client to opt-out EDNS0 Client Support
- [JSON-DoH] Honor DNSSEC OK flag for incoming DNS requests
- [JSON-DoH] Add support for non-standard response formats
- `X-Real-IP` is now used in logging if set by frontend load balancer
- Fix documentation
## Version 2.2.1
- Fix messy log

View File

@@ -15,7 +15,8 @@ ADD doh-client/doh-client.conf /doh-client.conf
RUN sed -i '$!N;s/"127.0.0.1:53",.*"127.0.0.1:5380",/":53",/;P;D' /doh-client.conf
RUN sed -i '$!N;s/"\[::1\]:53",.*"\[::1\]:5380",/":5380",/;P;D' /doh-client.conf
EXPOSE 53
EXPOSE 53/udp
EXPOSE 53/tcp
EXPOSE 5380
ENTRYPOINT ["/doh-client"]

View File

@@ -1,15 +1,11 @@
.PHONY: all clean install uninstall deps
.PHONY: all clean install uninstall
PREFIX = /usr/local
ifeq ($(GOROOT),)
GOBUILD = go build
GOGET = go get -d -v
GOGET_UPDATE = go get -d -u -v
GOBUILD = go build -ldflags "-s -w"
else
GOBUILD = $(GOROOT)/bin/go build
GOGET = $(GOROOT)/bin/go get -d -v
GOGET_UPDATE = $(GOROOT)/bin/go get -d -u -v
GOBUILD = $(GOROOT)/bin/go build -ldflags "-s -w"
endif
ifeq ($(shell uname),Darwin)
@@ -57,14 +53,8 @@ uninstall:
$(MAKE) -C launchd uninstall "DESTDIR=$(DESTDIR)"; \
fi
deps:
@# I am not sure if it is the correct way to keep the common library updated
$(GOGET_UPDATE) github.com/m13253/dns-over-https/doh-client/config
$(GOGET_UPDATE) github.com/m13253/dns-over-https/json-dns
$(GOGET) ./doh-client ./doh-server
doh-client/doh-client: deps doh-client/client.go doh-client/config/config.go doh-client/google.go doh-client/ietf.go doh-client/main.go doh-client/version.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
doh-client/doh-client: doh-client/client.go doh-client/config/config.go doh-client/google.go doh-client/ietf.go doh-client/main.go doh-client/version.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
cd doh-client && $(GOBUILD)
doh-server/doh-server: deps doh-server/config.go doh-server/google.go doh-server/ietf.go doh-server/main.go doh-server/server.go doh-server/version.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
doh-server/doh-server: doh-server/config.go doh-server/google.go doh-server/ietf.go doh-server/main.go doh-server/server.go doh-server/version.go json-dns/error.go json-dns/globalip.go json-dns/marshal.go json-dns/response.go json-dns/unmarshal.go
cd doh-server && $(GOBUILD)

355
Readme.md
View File

@@ -4,57 +4,78 @@ DNS-over-HTTPS
Client and server software to query DNS over HTTPS, using [Google DNS-over-HTTPS protocol](https://developers.google.com/speed/public-dns/docs/dns-over-https)
and [IETF DNS-over-HTTPS (RFC 8484)](https://www.rfc-editor.org/rfc/rfc8484.txt).
## Guide
## Guides
[Tutorial to setup your own DNS-over-HTTPS (DoH) server](https://www.aaflalo.me/2018/10/tutorial-setup-dns-over-https-server/). (Thanks to Antoine Aflalo)
- [Tutorial: Setup your own DNS-over-HTTPS (DoH) server](https://www.aaflalo.me/2018/10/tutorial-setup-dns-over-https-server/). (Thanks to Antoine Aflalo)
- [Tutorial: Setup your own Docker based DNS-over-HTTPS (DoH) server](https://github.com/satishweb/docker-doh/blob/master/README.md). (Thanks to Satish Gaikwad)
## Installing
### From Source
- Install [Go](https://golang.org), at least version 1.20. The newer, the better.
> Note for Debian/Ubuntu users: You need to set `$GOROOT` if you could not get your new version of Go selected by the Makefile.
Install [Go](https://golang.org), at least version 1.10.
(Note for Debian/Ubuntu users: You need to set `$GOROOT` if you could not get your new version of Go selected by the Makefile.)
First create an empty directory, used for `$GOPATH`:
mkdir ~/gopath
export GOPATH=~/gopath
To build the program, type:
make
To install DNS-over-HTTPS as Systemd services, type:
sudo make install
By default, [Google DNS over HTTPS](https://dns.google.com) is used. It should
- First create an empty directory, used for `$GOPATH`:
```bash
mkdir ~/gopath
export GOPATH=~/gopath
```
- To build the program, type:
```bash
make
```
- To install DNS-over-HTTPS as Systemd services, type:
```bash
sudo make install
```
- By default, [Google DNS over HTTPS](https://dns.google.com) is used. It should
work for most users (except for People's Republic of China). If you need to
modify the default settings, type:
```bash
sudoedit /etc/dns-over-https/doh-client.conf
```
- To automatically start DNS-over-HTTPS client as a system service, type:
```bash
sudo systemctl start doh-client.service
sudo systemctl enable doh-client.service
```
- Then, modify your DNS settings (usually with NetworkManager) to 127.0.0.1.
sudoedit /etc/dns-over-https/doh-client.conf
- To test your configuration, type:
```bash
dig www.google.com
Output:
;; SERVER: 127.0.0.1#53(127.0.0.1)
```
#### Uninstall
To automatically start DNS-over-HTTPS client as a system service, type:
- To uninstall, type:
```bash
sudo make uninstall
```
> Note: The configuration files are kept at `/etc/dns-over-https`. Remove them manually if you want.
sudo systemctl start doh-client.service
sudo systemctl enable doh-client.service
### Using docker image
```bash
docker run -d --name doh-server \
-p 8053:8053 \
-e UPSTREAM_DNS_SERVER="udp:208.67.222.222:53,udp:208.67.220.220:53" \
-e DOH_HTTP_PREFIX="/dns-query" \
-e DOH_SERVER_LISTEN=":8053" \
-e DOH_SERVER_TIMEOUT="10" \
-e DOH_SERVER_TRIES="3" \
-e DOH_SERVER_VERBOSE="false" \
satishweb/doh-server
```
Note: Multiple Upstream DNS server support was added in the container image on 2024-12-19.
Then, modify your DNS settings (usually with NetworkManager) to 127.0.0.1.
Feeling adventurous? Try the latest build:
To test your configuration, type:
- `m13253/dns-over-https-server:latest`
- `m13253/dns-over-https-client:latest`
dig www.google.com
## Logging
If it is OK, you will see:
;; SERVER: 127.0.0.1#53(127.0.0.1)
### Uninstalling
To uninstall, type:
sudo make uninstall
The configuration files are kept at `/etc/dns-over-https`. Remove them manually if you want.
All log lines (by either doh-client or doh-server) are written into `stderr`; you can view them using your OS tool of choice (`journalctl` when using systemd).
## Server Configuration
@@ -73,93 +94,215 @@ The following is a typical DNS-over-HTTPS architecture:
| doh-client +--+ Content Delivery Network +--+ (Apache, Nginx, Caddy) |
+--------------+ +--------------------------+ +------------------------+
Although DNS-over-HTTPS can work alone, a HTTP service muxer would be useful as
Although DNS-over-HTTPS can work alone, an HTTP service muxer would be useful as
you can host DNS-over-HTTPS along with other HTTPS services.
HTTP/2 with at least TLS v1.3 is recommended. OCSP stapling must be enabled,
otherwise DNS recursion may happen.
### Configuration file
The main configuration file is `doh-client.conf`.
**Server selectors.** If several upstream servers are set, one is selected according to `upstream_selector` for each request. With `upstream_selector = "random"`, a random upstream server will be chosen for each request.
```toml
# available selector: random (default) or weighted_round_robin or lvs_weighted_round_robin
upstream_selector = "random"
```
### Example configuration: Apache
```bash
SSLProtocol TLSv1.2
SSLHonorCipherOrder On
SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+3DES:!aNULL:!MD5:!DSS:!eNULL:!EXP:!LOW:!MD5
SSLUseStapling on
SSLStaplingCache shmcb:/var/lib/apache2/stapling_cache(512000)
SSLProtocol TLSv1.2
SSLHonorCipherOrder On
SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+3DES:!aNULL:!MD5:!DSS:!eNULL:!EXP:!LOW:!MD5
SSLUseStapling on
SSLStaplingCache shmcb:/var/lib/apache2/stapling_cache(512000)
<VirtualHost *:443>
ServerName MY_SERVER_NAME
Protocols h2 http/1.1
ProxyPass /dns-query http://[::1]:8053/dns-query
ProxyPassReverse /dns-query http://[::1]:8053/dns-query
</VirtualHost>
<VirtualHost *:443>
ServerName MY_SERVER_NAME
Protocols h2 http/1.1
ProxyPass /dns-query http://[::1]:8053/dns-query
ProxyPassReverse /dns-query http://[::1]:8053/dns-query
</VirtualHost>
```
(Credit: [Joan Moreau](https://github.com/m13253/dns-over-https/issues/51#issuecomment-526820884))
### Example configuration: Nginx
```bash
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name MY_SERVER_NAME;
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name MY_SERVER_NAME;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.3 requires nginx >= 1.13.0
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
ssl_early_data off; # 0-RTT, enable if desired - Requires nginx >= 1.15.4
resolver 1.1.1.1 valid=300s; # Replace with your local resolver
resolver_timeout 5s;
# HTTP Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=63072000";
ssl_certificate /path/to/your/server/certificates/fullchain.pem;
ssl_certificate_key /path/to/your/server/certificates/privkey.pem;
location /dns-query {
proxy_pass http://localhost:8053/dns-query;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.3 requires nginx >= 1.20.0
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
ssl_early_data off; # 0-RTT, enable if desired - Requires nginx >= 1.15.4
resolver 1.1.1.1 valid=300s; # Replace with your local resolver
resolver_timeout 5s;
# HTTP Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=63072000";
ssl_certificate /path/to/your/server/certificates/fullchain.pem;
ssl_certificate_key /path/to/your/server/certificates/privkey.pem;
location /dns-query {
proxy_pass http://localhost:8053/dns-query;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
(Credit: [Cipherli.st](https://cipherli.st/))
### Example configuration: Caddy
### Example configuration: Caddy (v2)
```bash
my.server.name {
reverse_proxy * localhost:8053
tls your@email.address
try_files {path} {path}/index.php /index.php?{query}
}
```
### Example configuration: Docker Compose + Traefik + Unbound (Raspberry Pi/Linux/Mac) [linux/amd64,linux/arm64,linux/arm/v7]
https://MY_SERVER_NAME {
log / syslog "{remote} - {user} [{when}] \"{method} {scheme}://{host}{uri} {proto}\" {status} {size} \"{>Referer}\" \"{>User-Agent}\" {>X-Forwarded-For}"
errors syslog
gzip
proxy /dns-query http://[::1]:18053 {
header_upstream Host {host}
header_upstream X-Real-IP {remote}
header_upstream X-Forwarded-For {>X-Forwarded-For},{remote}
header_upstream X-Forwarded-Proto {scheme}
}
root /var/www
tls {
ciphers ECDHE-ECDSA-WITH-CHACHA20-POLY1305 ECDHE-RSA-WITH-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256
curves X25519 p384 p521
must_staple
}
```yaml
version: '2.2'
networks:
default:
services:
proxy:
# The official v2 Traefik docker image
image: traefik:v2.3
hostname: proxy
networks:
- default
environment:
TRAEFIK_ACCESSLOG: "true"
TRAEFIK_API: "true"
TRAEFIK_PROVIDERS_DOCKER: "true"
TRAEFIK_API_INSECURE: "true"
TRAEFIK_PROVIDERS_DOCKER_NETWORK: "${STACK}_default"
# DNS provider specific environment variables for DNS Challenge using route53 (AWS)
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
AWS_REGION: ${AWS_REGION}
AWS_HOSTED_ZONE_ID: ${AWS_HOSTED_ZONE_ID}
ports:
# The HTTP port
- "80:80"
# The HTTPS port
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
command:
#- "--log.level=DEBUG"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
# Providers list:
# https://docs.traefik.io/https/acme/#providers
# https://go-acme.github.io/lego/dns/
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=route53"
# Enable below line to use staging letsencrypt server.
#- "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- ./data/proxy/certs:/certs
doh-server:
image: satishweb/doh-server:latest
hostname: doh-server
networks:
- default
environment:
# Enable below line to see more logs
# DEBUG: "1"
UPSTREAM_DNS_SERVER: "udp:unbound:53"
DOH_HTTP_PREFIX: "${DOH_HTTP_PREFIX}"
DOH_SERVER_LISTEN: ":${DOH_SERVER_LISTEN}"
DOH_SERVER_TIMEOUT: "10"
DOH_SERVER_TRIES: "3"
DOH_SERVER_VERBOSE: "false"
#volumes:
# - ./doh-server.conf:/server/doh-server.conf
# - ./app-config:/app-config
depends_on:
- unbound
labels:
- "traefik.enable=true"
- "traefik.http.routers.doh-server.rule=Host(`${SUBDOMAIN}.${DOMAIN}`) && Path(`${DOH_HTTP_PREFIX}`)"
- "traefik.http.services.doh-server.loadbalancer.server.port=${DOH_SERVER_LISTEN}"
- "traefik.http.middlewares.mw-doh-compression.compress=true"
- "traefik.http.routers.doh-server.tls=true"
- "traefik.http.middlewares.mw-doh-tls.headers.sslredirect=true"
- "traefik.http.middlewares.mw-doh-tls.headers.sslforcehost=true"
- "traefik.http.routers.doh-server.tls.certresolver=letsencrypt"
- "traefik.http.routers.doh-server.tls.domains[0].main=${DOMAIN}"
- "traefik.http.routers.doh-server.tls.domains[0].sans=${SUBDOMAIN}.${DOMAIN}"
# Protection from requests flood
- "traefik.http.middlewares.mw-doh-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.mw-doh-ratelimit.ratelimit.burst=50"
- "traefik.http.middlewares.mw-doh-ratelimit.ratelimit.period=10s"
unbound:
image: satishweb/unbound:latest
hostname: unbound
networks:
- default
ports:
# Disable these ports if DOH server is the only client
- 53:53/tcp
- 53:53/udp
volumes:
- ./unbound.sample.conf:/templates/unbound.sample.conf
- ./data/unbound/custom:/etc/unbound/custom
# Keep your custom.hosts file inside custom folder
#environment:
# DEBUG: "1"
````
> Complete Guide available at: https://github.com/satishweb/docker-doh
> IPV6 Support for Docker Compose based configuration TBA
### Example configuration: DNS-over-TLS
There is no native [DNS-over-TLS](https://en.wikipedia.org/wiki/DNS_over_TLS) support, but you can easily add it via nginx:
```
stream {
server {
listen *:853 ssl;
proxy_pass ipofyourdnsresolver:port #127.0.0.1:53
}
ssl_certificate /etc/letsencrypt/live/site.yourdomain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/site.yourdomain/privkey.pem;
}
```
The DoT service can also be provided by running a [STunnel](https://www.stunnel.org/) instance to wrap dnsmasq (or any other resolver of your choice, listening on a TCP port);
this approach does not need a stand-alone daemon to provide the DoT service.
## DNSSEC
DNS-over-HTTPS is compatible with DNSSEC, and requests DNSSEC signatures by
default. However signature validation is not built-in. It is highly recommended
default. However, signature validation is not built-in. It is highly recommended
that you install `unbound` or `bind` and pass results for them to validate DNS
records.
records. An instance of [Pi Hole](https://pi-hole.net) could also be used to validate DNS signatures as well as provide other capabilities.
## EDNS0-Client-Subnet (GeoDNS)
@@ -200,6 +343,10 @@ Currently supported features are:
- [X] EDNS0 large UDP packet (4 KiB by default)
- [X] EDNS0-Client-Subnet (/24 for IPv4, /56 for IPv6 by default)
## Known issues
* it does not work well with [dnscrypt-proxy](https://github.com/DNSCrypt/dnscrypt-proxy), you might want to use either (or fix the compatibility bugs by submitting PRs)
## The name of the project
This project is named "DNS-over-HTTPS" because it was written before the IETF DoH project. Although this project is compatible with IETF DoH, the project is not affiliated with IETF.

View File

@@ -25,6 +25,7 @@ package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"math/rand"
@@ -37,39 +38,40 @@ import (
"sync"
"time"
"github.com/m13253/dns-over-https/doh-client/config"
"github.com/m13253/dns-over-https/doh-client/selector"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"golang.org/x/net/http2"
"golang.org/x/net/idna"
"github.com/m13253/dns-over-https/v2/doh-client/config"
"github.com/m13253/dns-over-https/v2/doh-client/selector"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
type Client struct {
conf *config.Config
bootstrap []string
passthrough []string
udpClient *dns.Client
tcpClient *dns.Client
udpServers []*dns.Server
tcpServers []*dns.Server
bootstrapResolver *net.Resolver
httpClientLastCreate time.Time
cookieJar http.CookieJar
selector selector.Selector
httpClientMux *sync.RWMutex
tcpClient *dns.Client
bootstrapResolver *net.Resolver
udpClient *dns.Client
conf *config.Config
httpTransport *http.Transport
httpClient *http.Client
httpClientLastCreate time.Time
selector selector.Selector
udpServers []*dns.Server
tcpServers []*dns.Server
passthrough []string
bootstrap []string
}
type DNSRequest struct {
err error
response *http.Response
reply *dns.Msg
udpSize uint16
ednsClientAddress net.IP
ednsClientNetmask uint8
currentUpstream string
err error
ednsClientAddress net.IP
udpSize uint16
ednsClientNetmask uint8
}
func NewClient(conf *config.Config) (c *Client, err error) {
@@ -247,6 +249,7 @@ func (c *Client) newHTTPClient() error {
MaxIdleConnsPerHost: 10,
Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: time.Duration(c.conf.Other.Timeout) * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.conf.Other.TLSInsecureSkipVerify},
}
if c.conf.Other.NoIPv6 {
c.httpTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
@@ -305,7 +308,7 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
if len(r.Question) != 1 {
log.Println("Number of questions is not 1")
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return
@@ -356,7 +359,7 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
return
}
log.Println(err)
reply = jsonDNS.PrepareReply(r)
reply = jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return
@@ -425,7 +428,7 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
// https://developers.cloudflare.com/1.1.1.1/dns-over-https/request-structure/ says
// returns code will be 200 / 400 / 413 / 415 / 504, some server will return 503, so
// I think if status code is 5xx, upstream must has some problems
// I think if status code is 5xx, upstream must have some problems
/*if req.response.StatusCode/100 == 5 {
c.selector.ReportUpstreamStatus(upstream, selector.Medium)
}*/
@@ -471,7 +474,7 @@ func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddre
if err != nil {
return
}
if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) {
if ip := remoteAddr.IP; jsondns.IsGlobalIP(ip) {
if ipv4 := ip.To4(); ipv4 != nil {
ednsClientAddress = ipv4.Mask(ipv4Mask24)
ednsClientNetmask = 24

View File

@@ -47,15 +47,16 @@ type upstream struct {
}
type others struct {
Bootstrap []string `toml:"bootstrap"`
Passthrough []string `toml:"passthrough"`
Timeout uint `toml:"timeout"`
NoCookies bool `toml:"no_cookies"`
NoECS bool `toml:"no_ecs"`
NoIPv6 bool `toml:"no_ipv6"`
NoUserAgent bool `toml:"no_user_agent"`
Verbose bool `toml:"verbose"`
DebugHTTPHeaders []string `toml:"debug_http_headers"`
Bootstrap []string `toml:"bootstrap"`
Passthrough []string `toml:"passthrough"`
Timeout uint `toml:"timeout"`
NoCookies bool `toml:"no_cookies"`
NoECS bool `toml:"no_ecs"`
NoIPv6 bool `toml:"no_ipv6"`
NoUserAgent bool `toml:"no_user_agent"`
Verbose bool `toml:"verbose"`
DebugHTTPHeaders []string `toml:"debug_http_headers"`
TLSInsecureSkipVerify bool `toml:"insecure_tls_skip_verify"`
}
type Config struct {

View File

@@ -64,7 +64,7 @@ upstream_selector = "random"
# bootstrap server, please make this list empty.
bootstrap = [
# Google's resolver, bad ECS, good DNSSEC
# Google's resolver, good ECS, good DNSSEC
"8.8.8.8:53",
"8.8.4.4:53",
@@ -132,3 +132,9 @@ no_user_agent = false
# Enable logging
verbose = false
# insecure_tls_skip_verification will disable necessary TLS security verification.
# This option is designed for testing or development purposes,
# turning on this option on public Internet may cause your connection
# vulnerable to MITM attack.
insecure_tls_skip_verify = false

View File

@@ -27,16 +27,17 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/m13253/dns-over-https/doh-client/selector"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"github.com/m13253/dns-over-https/v2/doh-client/selector"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter, r *dns.Msg, isTCP bool, upstream *selector.Upstream) *DNSRequest {
@@ -44,7 +45,7 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
questionName := question.Name
questionClass := question.Qclass
if questionClass != dns.ClassINET {
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeRefused
w.WriteMsg(reply)
return &DNSRequest{
@@ -67,6 +68,9 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
udpSize := uint16(512)
if opt := r.IsEdns0(); opt != nil {
udpSize = opt.UDPSize()
if opt.Do() {
requestURL += "&do=1"
}
}
ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r)
@@ -74,10 +78,10 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
requestURL += fmt.Sprintf("&edns_client_subnet=%s/%d", ednsClientAddress.String(), ednsClientNetmask)
}
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
req, err := http.NewRequest(http.MethodGet, requestURL, http.NoBody)
if err != nil {
log.Println(err)
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
@@ -108,7 +112,7 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
if err != nil {
log.Println(err)
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
@@ -118,7 +122,7 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
return &DNSRequest{
response: resp,
reply: jsonDNS.PrepareReply(r),
reply: jsondns.PrepareReply(r),
udpSize: udpSize,
ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask,
@@ -137,7 +141,7 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
}
}
body, err := ioutil.ReadAll(req.response.Body)
body, err := io.ReadAll(req.response.Body)
if err != nil {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
@@ -145,7 +149,7 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
return
}
var respJSON jsonDNS.Response
var respJSON jsondns.Response
err = json.Unmarshal(body, &respJSON)
if err != nil {
log.Println(err)
@@ -157,8 +161,14 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
if respJSON.Status != dns.RcodeSuccess && respJSON.Comment != "" {
log.Printf("DNS error: %s\n", respJSON.Comment)
}
fixEmptyNames(&respJSON)
fullReply := jsonDNS.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask)
fullReply := jsondns.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask)
if isTCP {
fullReply.Truncate(dns.MaxMsgSize)
} else {
fullReply.Truncate(int(req.udpSize))
}
buf, err := fullReply.Pack()
if err != nil {
log.Println(err)
@@ -166,14 +176,21 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
w.WriteMsg(req.reply)
return
}
if !isTCP && len(buf) > int(req.udpSize) {
fullReply.Truncated = true
buf, err = fullReply.Pack()
if err != nil {
log.Println(err)
return
}
buf = buf[:req.udpSize]
}
w.Write(buf)
}
// Fix DNS response empty []RR.Name
// Additional section won't be rectified
// see: https://stackoverflow.com/questions/52136176/what-is-additional-section-in-dns-and-how-it-works
func fixEmptyNames(respJSON *jsondns.Response) {
for i := range respJSON.Answer {
if respJSON.Answer[i].Name == "" {
respJSON.Answer[i].Name = "."
}
}
for i := range respJSON.Authority {
if respJSON.Authority[i].Name == "" {
respJSON.Authority[i].Name = "."
}
}
}

View File

@@ -28,16 +28,17 @@ import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
"strings"
"time"
"github.com/m13253/dns-over-https/doh-client/selector"
jsonDNS "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"github.com/m13253/dns-over-https/v2/doh-client/selector"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter, r *dns.Msg, isTCP bool, upstream *selector.Upstream) *DNSRequest {
@@ -90,7 +91,7 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
requestBinary, err := r.Pack()
if err != nil {
log.Println(err)
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply)
return &DNSRequest{
@@ -104,10 +105,10 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
var req *http.Request
if len(requestURL) < 2048 {
req, err = http.NewRequest(http.MethodGet, requestURL, nil)
req, err = http.NewRequest(http.MethodGet, requestURL, http.NoBody)
if err != nil {
log.Println(err)
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
@@ -118,7 +119,7 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
req, err = http.NewRequest(http.MethodPost, upstream.URL, bytes.NewReader(requestBinary))
if err != nil {
log.Println(err)
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
@@ -149,7 +150,7 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
if err != nil {
log.Println(err)
reply := jsonDNS.PrepareReply(r)
reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply)
return &DNSRequest{
@@ -159,7 +160,7 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
return &DNSRequest{
response: resp,
reply: jsonDNS.PrepareReply(r),
reply: jsondns.PrepareReply(r),
udpSize: udpSize,
ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask,
@@ -178,7 +179,7 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
}
}
body, err := ioutil.ReadAll(req.response.Body)
body, err := io.ReadAll(req.response.Body)
if err != nil {
log.Printf("read error from upstream %s: %v\n", req.currentUpstream, err)
req.reply.Rcode = dns.RcodeServerFailure
@@ -231,6 +232,11 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
_ = fixRecordTTL(rr, timeDelta)
}
if isTCP {
fullReply.Truncate(dns.MaxMsgSize)
} else {
fullReply.Truncate(int(req.udpSize))
}
buf, err := fullReply.Pack()
if err != nil {
log.Printf("packing error with upstream %s: %v\n", req.currentUpstream, err)
@@ -238,15 +244,6 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
w.WriteMsg(req.reply)
return
}
if !isTCP && len(buf) > int(req.udpSize) {
fullReply.Truncated = true
buf, err = fullReply.Pack()
if err != nil {
log.Printf("re-packing error with upstream %s: %v\n", req.currentUpstream, err)
return
}
buf = buf[:req.udpSize]
}
_, err = w.Write(buf)
if err != nil {
log.Printf("failed to write to client: %v\n", err)

View File

@@ -26,21 +26,19 @@ package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"runtime"
"strconv"
"github.com/m13253/dns-over-https/doh-client/config"
"github.com/m13253/dns-over-https/v2/doh-client/config"
)
func checkPIDFile(pidFile string) (bool, error) {
retry:
f, err := os.OpenFile(pidFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
f, err := os.OpenFile(pidFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o666)
if os.IsExist(err) {
pidStr, err := ioutil.ReadFile(pidFile)
pidStr, err := os.ReadFile(pidFile)
if err != nil {
return false, err
}
@@ -64,7 +62,7 @@ retry:
return false, err
}
defer f.Close()
_, err = io.WriteString(f, strconv.FormatInt(int64(os.Getpid()), 10))
_, err = f.WriteString(strconv.FormatInt(int64(os.Getpid()), 10))
if err != nil {
return false, err
}

View File

@@ -80,7 +80,7 @@ func (ls *LVSWRRSelector) StartEvaluate() {
acceptType = "application/dns-message"
}
req, err := http.NewRequest(http.MethodGet, upstreamURL, nil)
req, err := http.NewRequest(http.MethodGet, upstreamURL, http.NoBody)
if err != nil {
/*log.Println("upstream:", upstreamURL, "type:", typeMap[upstream.Type], "check failed:", err)
continue*/

View File

@@ -73,7 +73,7 @@ func (ws *NginxWRRSelector) StartEvaluate() {
acceptType = "application/dns-message"
}
req, err := http.NewRequest(http.MethodGet, upstreamURL, nil)
req, err := http.NewRequest(http.MethodGet, upstreamURL, http.NoBody)
if err != nil {
/*log.Println("upstream:", upstreamURL, "type:", typeMap[upstream.Type], "check failed:", err)
continue*/
@@ -110,7 +110,7 @@ func (ws *NginxWRRSelector) StartEvaluate() {
}()
}
// nginx wrr like
// nginx wrr like.
func (ws *NginxWRRSelector) Get() *Upstream {
var (
total int32

View File

@@ -7,7 +7,7 @@ import (
)
func init() {
rand.Seed(time.Now().UnixNano())
rand.NewSource(time.Now().UnixNano())
}
type RandomSelector struct {

View File

@@ -3,12 +3,12 @@ package selector
type upstreamStatus int
const (
// when query upstream timeout, usually upstream is unavailable for a long time
// when query upstream timeout, usually upstream is unavailable for a long time.
Timeout upstreamStatus = iota
// when query upstream return 5xx response, upstream still alive, maybe just a lof of query for him
// when query upstream return 5xx response, upstream still alive, maybe just a lof of query for him.
Error
// when query upstream ok, means upstream is available
// when query upstream ok, means upstream is available.
OK
)

View File

@@ -24,6 +24,6 @@
package main
const (
VERSION = "2.2.1"
VERSION = "2.3.10"
USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)"
)

View File

@@ -31,17 +31,21 @@ import (
)
type config struct {
Listen []string `toml:"listen"`
LocalAddr string `toml:"local_addr"`
Cert string `toml:"cert"`
Key string `toml:"key"`
Path string `toml:"path"`
Upstream []string `toml:"upstream"`
Timeout uint `toml:"timeout"`
Tries uint `toml:"tries"`
Verbose bool `toml:"verbose"`
DebugHTTPHeaders []string `toml:"debug_http_headers"`
LogGuessedIP bool `toml:"log_guessed_client_ip"`
TLSClientAuthCA string `toml:"tls_client_auth_ca"`
LocalAddr string `toml:"local_addr"`
Cert string `toml:"cert"`
Key string `toml:"key"`
Path string `toml:"path"`
DebugHTTPHeaders []string `toml:"debug_http_headers"`
Listen []string `toml:"listen"`
Upstream []string `toml:"upstream"`
Timeout uint `toml:"timeout"`
Tries uint `toml:"tries"`
Verbose bool `toml:"verbose"`
LogGuessedIP bool `toml:"log_guessed_client_ip"`
ECSAllowNonGlobalIP bool `toml:"ecs_allow_non_global_ip"`
ECSUsePreciseIP bool `toml:"ecs_use_precise_ip"`
TLSClientAuth bool `toml:"tls_client_auth"`
}
func loadConfig(path string) (*config, error) {

View File

@@ -51,3 +51,28 @@ verbose = false
# Enable log IP from HTTPS-reverse proxy header: X-Forwarded-For or X-Real-IP
# Note: http uri/useragent log cannot be controlled by this config
log_guessed_client_ip = false
# By default, non global IP addresses are never forwarded to upstream servers.
# This is to prevent two things from happening:
# 1. the upstream server knowing your private LAN addresses;
# 2. the upstream server unable to provide geographically near results,
# or even fail to provide any result.
# However, if you are deploying a split tunnel corporation network
# environment, or for any other reason you want to inhibit this
# behavior and allow local (eg RFC1918) address to be forwarded,
# change the following option to "true".
ecs_allow_non_global_ip = false
# If ECS is added to the request, let the full IP address or
# cap it to 24 or 128 mask. This option is to be used only on private
# networks where knwoledge of the terminal endpoint may be required for
# security purposes (eg. DNS Firewalling). Not a good option on the
# internet where IP address may be used to identify the user and
# not only the approximate location.
ecs_use_precise_ip = false
# If DOH is used for a controlled network, it is possible to enable
# the client TLS certificate validation with a specific certificate
# authority used to sign any client one. Disabled by default.
# tls_client_auth = true
# tls_client_auth_ca = "root-ca-public.crt"

View File

@@ -34,9 +34,10 @@ import (
"strings"
"time"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"golang.org/x/net/idna"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
func (s *Server) parseRequestGoogle(ctx context.Context, w http.ResponseWriter, r *http.Request) *DNSRequest {
@@ -90,45 +91,14 @@ func (s *Server) parseRequestGoogle(ctx context.Context, w http.ResponseWriter,
if ednsClientSubnet == "0/0" {
ednsClientSubnet = "0.0.0.0/0"
}
slash := strings.IndexByte(ednsClientSubnet, '/')
if slash < 0 {
ednsClientAddress = net.ParseIP(ednsClientSubnet)
if ednsClientAddress == nil {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet),
}
var err error
ednsClientFamily, ednsClientAddress, ednsClientNetmask, err = parseSubnet(ednsClientSubnet)
if err != nil {
return &DNSRequest{
errcode: 400,
errtext: err.Error(),
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 56
}
} else {
ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash])
if ednsClientAddress == nil {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet),
}
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
} else {
ednsClientFamily = 2
}
netmask, err := strconv.ParseUint(ednsClientSubnet[slash+1:], 10, 8)
if err != nil {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet),
}
}
ednsClientNetmask = uint8(netmask)
}
} else {
ednsClientAddress = s.findClientIP(r)
@@ -169,12 +139,51 @@ func (s *Server) parseRequestGoogle(ctx context.Context, w http.ResponseWriter,
}
}
func parseSubnet(ednsClientSubnet string) (ednsClientFamily uint16, ednsClientAddress net.IP, ednsClientNetmask uint8, err error) {
slash := strings.IndexByte(ednsClientSubnet, '/')
if slash < 0 {
ednsClientAddress = net.ParseIP(ednsClientSubnet)
if ednsClientAddress == nil {
err = fmt.Errorf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet)
return
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
} else {
ednsClientFamily = 2
ednsClientNetmask = 56
}
} else {
ednsClientAddress = net.ParseIP(ednsClientSubnet[:slash])
if ednsClientAddress == nil {
err = fmt.Errorf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet)
return
}
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
} else {
ednsClientFamily = 2
}
netmask, err1 := strconv.ParseUint(ednsClientSubnet[slash+1:], 10, 8)
if err1 != nil {
err = fmt.Errorf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet)
return
}
ednsClientNetmask = uint8(netmask)
}
return
}
func (s *Server) generateResponseGoogle(ctx context.Context, w http.ResponseWriter, r *http.Request, req *DNSRequest) {
respJSON := jsonDNS.Marshal(req.response)
respJSON := jsondns.Marshal(req.response)
respStr, err := json.Marshal(respJSON)
if err != nil {
log.Println(err)
jsonDNS.FormatError(w, fmt.Sprintf("DNS packet parse failure (%s)", err.Error()), 500)
jsondns.FormatError(w, fmt.Sprintf("DNS packet parse failure (%s)", err.Error()), 500)
return
}

View File

@@ -28,7 +28,7 @@ import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
@@ -36,8 +36,9 @@ import (
"strings"
"time"
jsonDNS "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r *http.Request) *DNSRequest {
@@ -50,7 +51,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
}
}
if len(requestBinary) == 0 && (r.Header.Get("Content-Type") == "application/dns-message" || r.Header.Get("Content-Type") == "application/dns-udpwireformat") {
requestBinary, err = ioutil.ReadAll(r.Body)
requestBinary, err = io.ReadAll(r.Body)
if err != nil {
return &DNSRequest{
errcode: 400,
@@ -61,7 +62,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
if len(requestBinary) == 0 {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"dns\""),
errtext: "Invalid argument value: \"dns\"",
}
}
@@ -125,6 +126,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
}
}
isTailored := edns0Subnet == nil
if edns0Subnet == nil {
ednsClientFamily := uint16(0)
ednsClientAddress := s.findClientIP(r)
@@ -133,10 +135,20 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
ednsClientNetmask = 24
if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 32
} else {
ednsClientNetmask = 24
ednsClientAddress = ednsClientAddress.Mask(net.CIDRMask(24, 32))
}
} else {
ednsClientFamily = 2
ednsClientNetmask = 56
if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 128
} else {
ednsClientNetmask = 56
ednsClientAddress = ednsClientAddress.Mask(net.CIDRMask(56, 128))
}
}
edns0Subnet = new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET
@@ -156,12 +168,12 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
}
func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter, r *http.Request, req *DNSRequest) {
respJSON := jsonDNS.Marshal(req.response)
respJSON := jsondns.Marshal(req.response)
req.response.Id = req.transactionID
respBytes, err := req.response.Pack()
if err != nil {
log.Printf("DNS packet construct failure with upstream %s: %v\n", req.currentUpstream, err)
jsonDNS.FormatError(w, fmt.Sprintf("DNS packet construct failure (%s)", err.Error()), 500)
jsondns.FormatError(w, fmt.Sprintf("DNS packet construct failure (%s)", err.Error()), 500)
return
}
@@ -171,8 +183,6 @@ func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter
w.Header().Set("Last-Modified", now)
w.Header().Set("Vary", "Accept")
_ = s.patchFirefoxContentType(w, r, req)
if respJSON.HaveTTL {
if req.isTailored {
w.Header().Set("Cache-Control", "private, max-age="+strconv.FormatUint(uint64(respJSON.LeastTTL), 10))
@@ -192,10 +202,12 @@ func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter
}
}
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe.
func (s *Server) patchDNSCryptProxyReqID(w http.ResponseWriter, r *http.Request, requestBinary []byte) bool {
if strings.Contains(r.UserAgent(), "dnscrypt-proxy") && bytes.Equal(requestBinary, []byte("\xca\xfe\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00\x01\x00\x00\x29\x10\x00\x00\x00\x80\x00\x00\x00")) {
log.Println("DNSCrypt-Proxy detected. Patching response.")
if s.conf.Verbose {
log.Println("DNSCrypt-Proxy detected. Patching response.")
}
w.Header().Set("Content-Type", "application/dns-message")
w.Header().Set("Vary", "Accept, User-Agent")
now := time.Now().UTC().Format(http.TimeFormat)
@@ -205,15 +217,3 @@ func (s *Server) patchDNSCryptProxyReqID(w http.ResponseWriter, r *http.Request,
}
return false
}
// Workaround a bug causing Firefox 61-62 to reject responses with Content-Type = application/dns-message
func (s *Server) patchFirefoxContentType(w http.ResponseWriter, r *http.Request, req *DNSRequest) bool {
if strings.Contains(r.UserAgent(), "Firefox") && strings.Contains(r.Header.Get("Accept"), "application/dns-udpwireformat") && !strings.Contains(r.Header.Get("Accept"), "application/dns-message") {
log.Println("Firefox 61-62 detected. Patching response.")
w.Header().Set("Content-Type", "application/dns-udpwireformat")
w.Header().Set("Vary", "Accept, User-Agent")
req.isTailored = true
return true
}
return false
}

View File

@@ -26,8 +26,6 @@ package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"runtime"
@@ -36,9 +34,9 @@ import (
func checkPIDFile(pidFile string) (bool, error) {
retry:
f, err := os.OpenFile(pidFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
f, err := os.OpenFile(pidFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o666)
if os.IsExist(err) {
pidStr, err := ioutil.ReadFile(pidFile)
pidStr, err := os.ReadFile(pidFile)
if err != nil {
return false, err
}
@@ -62,7 +60,7 @@ retry:
return false, err
}
defer f.Close()
_, err = io.WriteString(f, strconv.FormatInt(int64(os.Getpid()), 10))
_, err = f.WriteString(strconv.FormatInt(int64(os.Getpid()), 10))
if err != nil {
return false, err
}

119
doh-server/parse_test.go Normal file
View File

@@ -0,0 +1,119 @@
/*
DNS-over-HTTPS
Copyright (C) 2017-2018 Star Brilliant <m13253@hotmail.com>
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.
*/
package main
import (
"testing"
"github.com/miekg/dns"
)
func TestParseCIDR(t *testing.T) {
t.Parallel()
for _, ednsClientSubnet := range []string{
"2001:db8::/0",
"2001:db8::/56",
"2001:db8::/129",
"2001:db8::",
"127.0.0.1/0",
"127.0.0.1/24",
"127.0.0.1/33",
"127.0.0.1",
"::ffff:7f00:1/0",
"::ffff:7f00:1/120",
"::ffff:7f00:1",
"127.0.0.1/0",
"127.0.0.1/24",
"127.0.0.1",
} {
_, ip, ipNet, err := parseSubnet(ednsClientSubnet)
if err != nil {
t.Errorf("ecs:%s ip:[%v] ipNet:[%v] err:[%v]", ednsClientSubnet, ip, ipNet, err)
}
}
}
func TestParseInvalidCIDR(t *testing.T) {
t.Parallel()
for _, ip := range []string{
"test",
"test/0",
"test/24",
"test/34",
"test/56",
"test/129",
} {
_, _, _, err := parseSubnet(ip)
if err == nil {
t.Errorf("expected error for %q", ip)
}
}
}
func TestEdns0SubnetParseCIDR(t *testing.T) {
t.Parallel()
// init dns Msg
msg := new(dns.Msg)
msg.Id = dns.Id()
msg.SetQuestion(dns.Fqdn("example.com"), 1)
// init edns0Subnet
edns0Subnet := new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET
edns0Subnet.SourceScope = 0
// init opt
opt := new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
opt.SetUDPSize(dns.DefaultMsgSize)
opt.Option = append(opt.Option, edns0Subnet)
msg.Extra = append(msg.Extra, opt)
for _, subnet := range []string{"::ffff:7f00:1/120", "127.0.0.1/24"} {
var err error
edns0Subnet.Family, edns0Subnet.Address, edns0Subnet.SourceNetmask, err = parseSubnet(subnet)
if err != nil {
t.Error(err)
continue
}
t.Log(msg.Pack())
}
// ------127.0.0.1/24-----
// [143 29 1 0 0 1 0 0 0 0 0 1 7 101 120 97 109 112 108 101 3 99 111 109 0 0 1 0 1 0
// opt start 0 41 16 0 0 0 0 0 0 11
// subnet start 0 8 0 7 0 1 24 0
// client subnet start 127 0 0]
// -----::ffff:7f00:1/120----
// [111 113 1 0 0 1 0 0 0 0 0 1 7 101 120 97 109 112 108 101 3 99 111 109 0 0 1 0 1 0
// opt start 0 41 16 0 0 0 0 0 0 23
// subnet start 0 8 0 19 0 2 120 0
// client subnet start 0 0 0 0 0 0 0 0 0 0 255 255 127 0 0]
}

View File

@@ -25,6 +25,8 @@ package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"math/rand"
@@ -35,8 +37,9 @@ import (
"time"
"github.com/gorilla/handlers"
jsonDNS "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
type Server struct {
@@ -50,11 +53,11 @@ type Server struct {
type DNSRequest struct {
request *dns.Msg
response *dns.Msg
transactionID uint16
currentUpstream string
isTailored bool
errcode int
errtext string
errcode int
transactionID uint16
isTailored bool
}
func NewServer(conf *config) (*Server, error) {
@@ -107,12 +110,48 @@ func (s *Server) Start() error {
if s.conf.Verbose {
servemux = handlers.CombinedLoggingHandler(os.Stdout, servemux)
}
var clientCAPool *x509.CertPool
if s.conf.TLSClientAuth {
if s.conf.TLSClientAuthCA != "" {
clientCA, err := os.ReadFile(s.conf.TLSClientAuthCA)
if err != nil {
log.Fatalf("Reading certificate for client authentication has failed: %v", err)
}
clientCAPool = x509.NewCertPool()
clientCAPool.AppendCertsFromPEM(clientCA)
log.Println("Certificate loaded for client TLS authentication")
} else {
log.Fatalln("TLS client authentication requires both tls_client_auth and tls_client_auth_ca, exiting.")
}
}
results := make(chan error, len(s.conf.Listen))
for _, addr := range s.conf.Listen {
go func(addr string) {
var err error
if s.conf.Cert != "" || s.conf.Key != "" {
err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux)
if clientCAPool != nil {
srvtls := &http.Server{
Handler: servemux,
Addr: addr,
TLSConfig: &tls.Config{
ClientCAs: clientCAPool,
ClientAuth: tls.RequireAndVerifyClientCert,
GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
c, err := tls.LoadX509KeyPair(s.conf.Cert, s.conf.Key)
if err != nil {
fmt.Printf("Error loading server certificate key pair: %v\n", err)
return nil, err
}
return &c, nil
},
},
}
err = srvtls.ListenAndServeTLS("", "")
} else {
err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux)
}
} else {
err = http.ListenAndServe(addr, servemux)
}
@@ -136,6 +175,18 @@ func (s *Server) Start() error {
func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
if strings.ContainsRune(realIP, ':') {
r.RemoteAddr = "[" + realIP + "]:0"
} else {
r.RemoteAddr = realIP + ":0"
}
_, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
r.RemoteAddr = realIP
}
}
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS, POST")
w.Header().Set("Access-Control-Allow-Origin", "*")
@@ -204,23 +255,22 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
} else if contentType == "application/dns-udpwireformat" {
req = s.parseRequestIETF(ctx, w, r)
} else {
jsonDNS.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 415)
jsondns.FormatError(w, fmt.Sprintf("Invalid argument value: \"ct\" = %q", contentType), 415)
return
}
if req.errcode == 444 {
return
}
if req.errcode != 0 {
jsonDNS.FormatError(w, req.errtext, req.errcode)
jsondns.FormatError(w, req.errtext, req.errcode)
return
}
req = s.patchRootRD(req)
var err error
req, err = s.doDNSQuery(ctx, req)
err := s.doDNSQuery(ctx, req)
if err != nil {
jsonDNS.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503)
jsondns.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503)
return
}
@@ -234,12 +284,17 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) findClientIP(r *http.Request) net.IP {
noEcs := r.URL.Query().Get("no_ecs")
if strings.EqualFold(noEcs, "true") {
return nil
}
XForwardedFor := r.Header.Get("X-Forwarded-For")
if XForwardedFor != "" {
for _, addr := range strings.Split(XForwardedFor, ",") {
addr = strings.TrimSpace(addr)
ip := net.ParseIP(addr)
if jsonDNS.IsGlobalIP(ip) {
if jsondns.IsGlobalIP(ip) {
return ip
}
}
@@ -248,21 +303,23 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
if XRealIP != "" {
addr := strings.TrimSpace(XRealIP)
ip := net.ParseIP(addr)
if jsonDNS.IsGlobalIP(ip) {
if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) {
return ip
}
}
remoteAddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err != nil {
return nil
}
if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) {
ip := remoteAddr.IP
if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) {
return ip
}
return nil
}
// Workaround a bug causing Unbound to refuse returning anything about the root
// Workaround a bug causing Unbound to refuse returning anything about the root.
func (s *Server) patchRootRD(req *DNSRequest) *DNSRequest {
for _, question := range req.request.Question {
if question.Name == "." {
@@ -272,7 +329,7 @@ func (s *Server) patchRootRD(req *DNSRequest) *DNSRequest {
return req
}
// Return the position index for the question of qtype from a DNS msg, otherwise return -1
// Return the position index for the question of qtype from a DNS msg, otherwise return -1.
func (s *Server) indexQuestionType(msg *dns.Msg, qtype uint16) int {
for i, question := range msg.Question {
if question.Qtype == qtype {
@@ -282,8 +339,7 @@ func (s *Server) indexQuestionType(msg *dns.Msg, qtype uint16) int {
return -1
}
func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequest, err error) {
// TODO(m13253): Make ctx work. Waiting for a patch for ExchangeContext from miekg/dns.
func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (err error) {
numServers := len(s.conf.Upstream)
for i := uint(0); i < s.conf.Tries; i++ {
req.currentUpstream = s.conf.Upstream[rand.Intn(numServers)]
@@ -293,34 +349,34 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
switch t {
default:
log.Printf("invalid DNS type %q in upstream %q", t, upstream)
return nil, &configError{"invalid DNS type"}
return &configError{"invalid DNS type"}
// Use DNS-over-TLS (DoT) if configured to do so
case "tcp-tls":
req.response, _, err = s.tcpClientTLS.Exchange(req.request, upstream)
req.response, _, err = s.tcpClientTLS.ExchangeContext(ctx, req.request, upstream)
case "tcp", "udp":
// Use TCP if always configured to or if the Query type dictates it (AXFR)
if t == "tcp" || (s.indexQuestionType(req.request, dns.TypeAXFR) > -1) {
req.response, _, err = s.tcpClient.Exchange(req.request, upstream)
req.response, _, err = s.tcpClient.ExchangeContext(ctx, req.request, upstream)
} else {
req.response, _, err = s.udpClient.Exchange(req.request, upstream)
req.response, _, err = s.udpClient.ExchangeContext(ctx, req.request, upstream)
if err == nil && req.response != nil && req.response.Truncated {
log.Println(err)
req.response, _, err = s.tcpClient.Exchange(req.request, upstream)
req.response, _, err = s.tcpClient.ExchangeContext(ctx, req.request, upstream)
}
// Retry with TCP if this was an IXFR request and we only received an SOA
// Retry with TCP if this was an IXFR request, and we only received an SOA
if (s.indexQuestionType(req.request, dns.TypeIXFR) > -1) &&
(len(req.response.Answer) == 1) &&
(req.response.Answer[0].Header().Rrtype == dns.TypeSOA) {
req.response, _, err = s.tcpClient.Exchange(req.request, upstream)
req.response, _, err = s.tcpClient.ExchangeContext(ctx, req.request, upstream)
}
}
}
if err == nil {
return req, nil
return nil
}
log.Printf("DNS error from upstream %s: %s\n", req.currentUpstream, err.Error())
}
return req, err
return err
}

View File

@@ -24,6 +24,6 @@
package main
const (
VERSION = "2.2.1"
VERSION = "2.3.10"
USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)"
)

24
go.mod
View File

@@ -1,12 +1,20 @@
module github.com/m13253/dns-over-https
module github.com/m13253/dns-over-https/v2
go 1.12
go 1.24
require (
github.com/BurntSushi/toml v0.3.1
github.com/gorilla/handlers v1.4.0
github.com/miekg/dns v1.1.22
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
github.com/BurntSushi/toml v1.5.0
github.com/gorilla/handlers v1.5.2
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc
github.com/miekg/dns v1.1.66
golang.org/x/net v0.40.0
)
require (
github.com/felixge/httpsnoop v1.0.4 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
)

64
go.sum
View File

@@ -1,36 +1,28 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA=
github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.22 h1:Jm64b3bO9kP43ddLjL2EY3Io6bmy1qGb9Xxz6TqS6rc=
github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a h1:Yu34BogBivvmu7SAzHHaB9nZWH5D1C+z3F1jyIaYZSQ=
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg=
golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc h1:RhT2pjLo3EVRmldbEcBdeRA7CGPWsNEJC+Y/N1aXQbg=
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI=
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -21,7 +21,7 @@
DEALINGS IN THE SOFTWARE.
*/
package jsonDNS
package jsondns
import (
"encoding/json"
@@ -38,11 +38,11 @@ type dnsError struct {
func FormatError(w http.ResponseWriter, comment string, errcode int) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
errJson := dnsError{
errJSON := dnsError{
Status: dns.RcodeServerFailure,
Comment: comment,
}
errStr, err := json.Marshal(errJson)
errStr, err := json.Marshal(errJSON)
if err != nil {
log.Fatalln(err)
}

View File

@@ -21,109 +21,110 @@
DEALINGS IN THE SOFTWARE.
*/
package jsonDNS
package jsondns
import (
"net"
"github.com/infobloxopen/go-trees/iptree"
)
// RFC6890
var localIPv4Nets = []net.IPNet{
// This host on this network
net.IPNet{
net.IP{0, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Private-Use Networks
net.IPNet{
net.IP{10, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Shared Address Space
net.IPNet{
net.IP{100, 64, 0, 0},
net.IPMask{255, 192, 0, 0},
},
// Loopback
net.IPNet{
net.IP{127, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Link Local
net.IPNet{
net.IP{169, 254, 0, 0},
net.IPMask{255, 255, 0, 0},
},
// Private-Use Networks
net.IPNet{
net.IP{172, 16, 0, 0},
net.IPMask{255, 240, 0, 0},
},
// DS-Lite
net.IPNet{
net.IP{192, 0, 0, 0},
net.IPMask{255, 255, 255, 248},
},
// 6to4 Relay Anycast
net.IPNet{
net.IP{192, 88, 99, 0},
net.IPMask{255, 255, 255, 0},
},
// Private-Use Networks
net.IPNet{
net.IP{192, 168, 0, 0},
net.IPMask{255, 255, 0, 0},
},
// Reserved for Future Use & Limited Broadcast
net.IPNet{
net.IP{240, 0, 0, 0},
net.IPMask{240, 0, 0, 0},
},
}
var defaultFilter *iptree.Tree
// RFC6890
var localIPv6Nets = []net.IPNet{
func init() {
defaultFilter = iptree.NewTree()
// RFC6890
// This host on this network
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{0, 0, 0, 0},
Mask: net.IPMask{255, 0, 0, 0},
}, struct{}{})
// Private-Use Networks
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{10, 0, 0, 0},
Mask: net.IPMask{255, 0, 0, 0},
}, struct{}{})
// Shared Address Space
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{100, 64, 0, 0},
Mask: net.IPMask{255, 192, 0, 0},
}, struct{}{})
// Loopback
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{127, 0, 0, 0},
Mask: net.IPMask{255, 0, 0, 0},
}, struct{}{})
// Link Local
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{169, 254, 0, 0},
Mask: net.IPMask{255, 255, 0, 0},
}, struct{}{})
// Private-Use Networks
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{172, 16, 0, 0},
Mask: net.IPMask{255, 240, 0, 0},
}, struct{}{})
// DS-Lite
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{192, 0, 0, 0},
Mask: net.IPMask{255, 255, 255, 248},
}, struct{}{})
// 6to4 Relay Anycast
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{192, 88, 99, 0},
Mask: net.IPMask{255, 255, 255, 0},
}, struct{}{})
// Private-Use Networks
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{192, 168, 0, 0},
Mask: net.IPMask{255, 255, 0, 0},
}, struct{}{})
// Reserved for Future Use & Limited Broadcast
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{240, 0, 0, 0},
Mask: net.IPMask{240, 0, 0, 0},
}, struct{}{})
// RFC6890
// Unspecified & Loopback Address
net.IPNet{
net.IP{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe},
},
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe},
}, struct{}{})
// Discard-Only Prefix
net.IPNet{
net.IP{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}, struct{}{})
// Unique-Local
net.IPNet{
net.IP{0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Mask: net.IPMask{0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}, struct{}{})
// Linked-Scoped Unicast
net.IPNet{
net.IP{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
net.IPMask{0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Mask: net.IPMask{0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}, struct{}{})
}
func IsGlobalIP(ip net.IP) bool {
if ip == nil {
return false
}
if ipv4 := ip.To4(); len(ipv4) == net.IPv4len {
for _, ipnet := range localIPv4Nets {
if ipnet.Contains(ip) {
return false
}
}
return true
}
if len(ip) == net.IPv6len {
for _, ipnet := range localIPv6Nets {
if ipnet.Contains(ip) {
return false
}
}
return true
}
return true
_, contained := defaultFilter.GetByIP(ip)
return !contained
}

34
json-dns/globalip_test.go Normal file
View File

@@ -0,0 +1,34 @@
package jsondns
import (
"fmt"
"net"
)
func ExampleIsGlobalIP() {
fmt.Println(IsGlobalIP(net.ParseIP("127.0.0.1")))
fmt.Println(IsGlobalIP(net.IP{192, 168, 1, 1}))
fmt.Println(IsGlobalIP(net.ParseIP("8.8.8.8")))
fmt.Println(IsGlobalIP(net.IP{8, 8, 4, 4}))
fmt.Println(IsGlobalIP(net.ParseIP("::1")))
fmt.Println(IsGlobalIP(net.IP{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))
fmt.Println(IsGlobalIP(net.ParseIP("2001:4860:4860::8888")))
fmt.Println(IsGlobalIP(net.IP{0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x44}))
fmt.Println(IsGlobalIP(net.ParseIP("::ffff:127.0.0.1")))
fmt.Println(IsGlobalIP(net.IP{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 192, 168, 1, 1}))
fmt.Println(IsGlobalIP(net.ParseIP("::ffff:808:808")))
fmt.Println(IsGlobalIP(net.IP{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 8, 8, 4, 4}))
// Output:
// false
// false
// true
// true
// false
// false
// true
// true
// false
// false
// true
// true
}

View File

@@ -21,7 +21,7 @@
DEALINGS IN THE SOFTWARE.
*/
package jsonDNS
package jsondns
import (
"net"

View File

@@ -21,12 +21,32 @@
DEALINGS IN THE SOFTWARE.
*/
package jsonDNS
package jsondns
import (
"encoding/json"
"time"
)
type QuestionList []Question
func (ql *QuestionList) UnmarshalJSON(b []byte) error {
// Fix variant question response in Response.Question
//
// Solution taken from:
// https://engineering.bitnami.com/articles/dealing-with-json-with-non-homogeneous-types-in-go.html
// https://archive.is/NU4zR
if len(b) > 0 && b[0] == '[' {
return json.Unmarshal(b, (*[]Question)(ql))
}
var q Question
if err := json.Unmarshal(b, &q); err != nil {
return err
}
*ql = []Question{q}
return nil
}
type Response struct {
// Standard DNS response code (32 bit integer)
Status uint32 `json:"Status"`
@@ -40,13 +60,13 @@ type Response struct {
// FIXME: We don't have DNSSEC yet! This bit is not reliable!
AD bool `json:"AD"`
// Whether the client asked to disable DNSSEC
CD bool `json:"CD"`
Question []Question `json:"Question"`
Answer []RR `json:"Answer,omitempty"`
Authority []RR `json:"Authority,omitempty"`
Additional []RR `json:"Additional,omitempty"`
Comment string `json:"Comment,omitempty"`
EdnsClientSubnet string `json:"edns_client_subnet,omitempty"`
CD bool `json:"CD"`
Question QuestionList `json:"Question"`
Answer []RR `json:"Answer,omitempty"`
Authority []RR `json:"Authority,omitempty"`
Additional []RR `json:"Additional,omitempty"`
Comment string `json:"Comment,omitempty"`
EdnsClientSubnet string `json:"edns_client_subnet,omitempty"`
// Least time-to-live
HaveTTL bool `json:"-"`
LeastTTL uint32 `json:"-"`

View File

@@ -21,7 +21,7 @@
DEALINGS IN THE SOFTWARE.
*/
package jsonDNS
package jsondns
import (
"fmt"

View File

@@ -4,15 +4,18 @@ Documentation=https://github.com/m13253/dns-over-https
After=network.target
Before=nss-lookup.target
Wants=nss-lookup.target
StartLimitIntervalSec=0
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
ExecStart=/usr/local/bin/doh-client -conf /etc/dns-over-https/doh-client.conf
LimitNOFILE=1048576
Restart=always
RestartSec=3
RestartSec=1s
RestartMaxDelaySec=76s
RestartSteps=9
Type=simple
User=nobody
DynamicUser=yes
[Install]
WantedBy=multi-user.target

View File

@@ -2,15 +2,18 @@
Description=DNS-over-HTTPS Server
Documentation=https://github.com/m13253/dns-over-https
After=network.target
StartLimitIntervalSec=0
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
ExecStart=/usr/local/bin/doh-server -conf /etc/dns-over-https/doh-server.conf
LimitNOFILE=1048576
Restart=always
RestartSec=3
RestartSec=1s
RestartMaxDelaySec=76s
RestartSteps=9
Type=simple
User=nobody
DynamicUser=yes
[Install]
WantedBy=multi-user.target