Compare commits

..

142 Commits

Author SHA1 Message Date
Star Brilliant
82c5f0d327 Merge pull request #185 from shahradelahi/feature/bind-to-interface
Some checks failed
Docker / docker (client) (push) Failing after 33s
Docker / docker (server) (push) Failing after 31s
Go build for Linux / Build (push) Failing after 2m40s
2026-03-17 14:06:24 +00:00
Star Brilliant
602b3d6322 Merge pull request #186 from vinnyperella/patch-2 2026-01-23 18:43:00 +00:00
Vinny
db0bd43256 chore: upgrade dependencies 2026-01-23 17:26:28 +00:00
Shahrad Elahi
06e3d67f79 feat: support dual-stack for interface binding 2026-01-23 16:04:57 +00:00
Shahrad Elahi
d27aef852d feat: add option to bind outgoing connections to a specific interface
This adds a new `interface` configuration option to `doh-client` that allows users to specify a network interface for all outgoing DNS queries (including bootstrap and passthrough traffic).
2026-01-23 01:41:28 +00:00
Star Brilliant
6c561eb412 Merge pull request #181 from vinnyperella/patch-1 2025-11-18 20:38:06 +00:00
Vinny
381bf28a69 chore: upgrade dependencies 2025-11-18 16:19:55 +00:00
Star Brilliant
0b0651a015 Merge pull request #178 from vinnyperella/patch-1 2025-09-16 16:12:40 +00:00
Vinny
3130a747f8 chore: upgrade dependencies 2025-09-16 13:17:20 +00:00
Star Brilliant
fe9f9f9ad2 Merge pull request #176 from vinnyperella/patch-1 2025-06-17 17:18:03 +00:00
Vinny
00c6af00ed chore: upgrade dependencies 2025-06-17 17:15:43 +00:00
Star Brilliant
04f3e029ac Merge pull request #172 from bfahrenfort/patch-1
config: Add captive portal domains
2025-05-28 21:10:00 +00:00
bfahrenfort
87b3eedded doh-client: lint 2025-05-28 15:26:28 -05:00
Star Brilliant
c57a45deaa Merge pull request #174 from m13253/m13253/restart-backoff
Move StartLimitIntervalSec=0 from [Service] to [Unit]
2025-05-28 00:37:56 +00:00
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
bfahrenfort
dfba0c36c5 config: Add captive portal domains 2025-05-12 15:48:41 -05: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
sanyo
0c878a6ad7 change git ignore 2020-07-29 10:50:38 +08:00
sanyo
31ea7c520d Use ipTree to determine the global IP 2020-07-16 17:11:34 +08:00
36 changed files with 949 additions and 400 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.25.6
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') }}

1
.gitignore vendored
View File

@@ -17,3 +17,4 @@ doh-server/doh-server
.glide/ .glide/
.idea/ .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,45 @@ This Changelog records major changes between versions.
Not all changes are recorded. Please check git log for details. 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 ## Version 2.2.2
- Allow client to opt-out EDNS0 Client Support - Allow client to opt-out EDNS0 Client Support

View File

@@ -1,15 +1,11 @@
.PHONY: all clean install uninstall deps .PHONY: all clean install uninstall
PREFIX = /usr/local PREFIX = /usr/local
ifeq ($(GOROOT),) ifeq ($(GOROOT),)
GOBUILD = go build GOBUILD = go build -ldflags "-s -w"
GOGET = go get -d -v
GOGET_UPDATE = go get -d -u -v
else else
GOBUILD = $(GOROOT)/bin/go build GOBUILD = $(GOROOT)/bin/go build -ldflags "-s -w"
GOGET = $(GOROOT)/bin/go get -d -v
GOGET_UPDATE = $(GOROOT)/bin/go get -d -u -v
endif endif
ifeq ($(shell uname),Darwin) ifeq ($(shell uname),Darwin)
@@ -57,14 +53,8 @@ uninstall:
$(MAKE) -C launchd uninstall "DESTDIR=$(DESTDIR)"; \ $(MAKE) -C launchd uninstall "DESTDIR=$(DESTDIR)"; \
fi fi
deps: 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
@# 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
cd doh-client && $(GOBUILD) 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) cd doh-server && $(GOBUILD)

View File

@@ -11,8 +11,8 @@ and [IETF DNS-over-HTTPS (RFC 8484)](https://www.rfc-editor.org/rfc/rfc8484.txt)
## Installing ## Installing
### From Source ### From Source
- Install [Go](https://golang.org), at least version 1.10. - 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.) > 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`: - First create an empty directory, used for `$GOPATH`:
```bash ```bash
@@ -56,16 +56,26 @@ sudo make uninstall
### Using docker image ### Using docker image
```bash ```bash
docker run -itd --name doh-server \ docker run -d --name doh-server \
-p 8053:8053 \ -p 8053:8053 \
-e UPSTREAM_DNS_SERVER="udp:8.8.8.8:53" \ -e UPSTREAM_DNS_SERVER="udp:208.67.222.222:53,udp:208.67.220.220:53" \
-e DOH_HTTP_PREFIX="/dns-query" -e DOH_HTTP_PREFIX="/dns-query" \
-e DOH_SERVER_LISTEN=":8053" -e DOH_SERVER_LISTEN=":8053" \
-e DOH_SERVER_TIMEOUT="10" -e DOH_SERVER_TIMEOUT="10" \
-e DOH_SERVER_TRIES="3" -e DOH_SERVER_TRIES="3" \
-e DOH_SERVER_VERBOSE="false" -e DOH_SERVER_VERBOSE="false" \
satishweb/doh-server satishweb/doh-server
``` ```
Note: Multiple Upstream DNS server support was added in the container image on 2024-12-19.
Feeling adventurous? Try the latest build:
- `m13253/dns-over-https-server:latest`
- `m13253/dns-over-https-client:latest`
## Logging
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 ## Server Configuration
@@ -84,7 +94,7 @@ The following is a typical DNS-over-HTTPS architecture:
| doh-client +--+ Content Delivery Network +--+ (Apache, Nginx, Caddy) | | 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. 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, HTTP/2 with at least TLS v1.3 is recommended. OCSP stapling must be enabled,
@@ -127,7 +137,7 @@ server {
server_tokens off; server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.3 requires nginx >= 1.13.0 ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.3 requires nginx >= 1.20.0
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096 ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096
ssl_ciphers EECDH+AESGCM:EDH+AESGCM; ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
@@ -170,10 +180,11 @@ my.server.name {
version: '2.2' version: '2.2'
networks: networks:
default: default:
services: services:
proxy: proxy:
# The official v2 Traefik docker image # The official v2 Traefik docker image
image: traefik:v2.2 image: traefik:v2.3
hostname: proxy hostname: proxy
networks: networks:
- default - default
@@ -266,14 +277,30 @@ services:
> Complete Guide available at: https://github.com/satishweb/docker-doh > Complete Guide available at: https://github.com/satishweb/docker-doh
> No IPV6 Support: Docker Swarm does not support IPV6 as of yet. Issue is logged [here](https://github.com/moby/moby/issues/24379)
> IPV6 Support for Docker Compose based configuration TBA > 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 ## DNSSEC
DNS-over-HTTPS is compatible with DNSSEC, and requests DNSSEC signatures by 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 that you install `unbound` or `bind` and pass results for them to validate DNS
records. An instance of [Pi Hole](https://pi-hole.net) could also be used to validate DNS signatures as well as provide other capabilities. records. An instance of [Pi Hole](https://pi-hole.net) could also be used to validate DNS signatures as well as provide other capabilities.
@@ -316,6 +343,10 @@ Currently supported features are:
- [X] EDNS0 large UDP packet (4 KiB by default) - [X] EDNS0 large UDP packet (4 KiB by default)
- [X] EDNS0-Client-Subnet (/24 for IPv4, /56 for IPv6 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 ## 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. 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 ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
@@ -37,39 +38,40 @@ import (
"sync" "sync"
"time" "time"
"github.com/m13253/dns-over-https/doh-client/config"
"github.com/m13253/dns-over-https/doh-client/selector"
jsonDNS "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/idna" "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 { type Client struct {
conf *config.Config httpClientLastCreate time.Time
bootstrap []string
passthrough []string
udpClient *dns.Client
tcpClient *dns.Client
udpServers []*dns.Server
tcpServers []*dns.Server
bootstrapResolver *net.Resolver
cookieJar http.CookieJar cookieJar http.CookieJar
selector selector.Selector
httpClientMux *sync.RWMutex httpClientMux *sync.RWMutex
tcpClient *dns.Client
bootstrapResolver *net.Resolver
udpClient *dns.Client
conf *config.Config
httpTransport *http.Transport httpTransport *http.Transport
httpClient *http.Client httpClient *http.Client
httpClientLastCreate time.Time udpServers []*dns.Server
selector selector.Selector tcpServers []*dns.Server
passthrough []string
bootstrap []string
} }
type DNSRequest struct { type DNSRequest struct {
err error
response *http.Response response *http.Response
reply *dns.Msg reply *dns.Msg
udpSize uint16
ednsClientAddress net.IP
ednsClientNetmask uint8
currentUpstream string currentUpstream string
err error ednsClientAddress net.IP
udpSize uint16
ednsClientNetmask uint8
} }
func NewClient(conf *config.Config) (c *Client, err error) { func NewClient(conf *config.Config) (c *Client, err error) {
@@ -88,6 +90,29 @@ func NewClient(conf *config.Config) (c *Client, err error) {
Net: "tcp", Net: "tcp",
Timeout: time.Duration(conf.Other.Timeout) * time.Second, Timeout: time.Duration(conf.Other.Timeout) * time.Second,
} }
if c.conf.Other.Interface != "" {
localV4, localV6, err := c.getInterfaceIPs()
if err != nil {
return nil, fmt.Errorf("failed to get interface IPs for %s: %v", c.conf.Other.Interface, err)
}
var localAddr net.IP
if localV4 != nil {
localAddr = localV4
} else {
localAddr = localV6
}
c.udpClient.Dialer = &net.Dialer{
Timeout: time.Duration(conf.Other.Timeout) * time.Second,
LocalAddr: &net.UDPAddr{IP: localAddr},
}
c.tcpClient.Dialer = &net.Dialer{
Timeout: time.Duration(conf.Other.Timeout) * time.Second,
LocalAddr: &net.TCPAddr{IP: localAddr},
}
}
for _, addr := range conf.Listen { for _, addr := range conf.Listen {
c.udpServers = append(c.udpServers, &dns.Server{ c.udpServers = append(c.udpServers, &dns.Server{
Addr: addr, Addr: addr,
@@ -118,6 +143,38 @@ func NewClient(conf *config.Config) (c *Client, err error) {
PreferGo: true, PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) { Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer var d net.Dialer
if c.conf.Other.Interface != "" {
localV4, localV6, err := c.getInterfaceIPs()
if err != nil {
log.Printf("Bootstrap dial warning: %v", err)
} else {
numServers := len(c.bootstrap)
bootstrap := c.bootstrap[rand.Intn(numServers)]
host, _, _ := net.SplitHostPort(bootstrap)
ip := net.ParseIP(host)
if ip != nil {
if ip.To4() != nil {
if localV4 != nil {
if strings.HasPrefix(network, "udp") {
d.LocalAddr = &net.UDPAddr{IP: localV4}
} else {
d.LocalAddr = &net.TCPAddr{IP: localV4}
}
}
} else {
if localV6 != nil {
if strings.HasPrefix(network, "udp") {
d.LocalAddr = &net.UDPAddr{IP: localV6}
} else {
d.LocalAddr = &net.TCPAddr{IP: localV6}
}
}
}
}
conn, err := d.DialContext(ctx, network, bootstrap)
return conn, err
}
}
numServers := len(c.bootstrap) numServers := len(c.bootstrap)
bootstrap := c.bootstrap[rand.Intn(numServers)] bootstrap := c.bootstrap[rand.Intn(numServers)]
conn, err := d.DialContext(ctx, network, bootstrap) conn, err := d.DialContext(ctx, network, bootstrap)
@@ -233,30 +290,92 @@ func (c *Client) newHTTPClient() error {
if c.httpTransport != nil { if c.httpTransport != nil {
c.httpTransport.CloseIdleConnections() c.httpTransport.CloseIdleConnections()
} }
dialer := &net.Dialer{
localV4, localV6, err := c.getInterfaceIPs()
if err != nil {
log.Printf("Interface binding error: %v", err)
return err
}
baseDialer := &net.Dialer{
Timeout: time.Duration(c.conf.Other.Timeout) * time.Second, Timeout: time.Duration(c.conf.Other.Timeout) * time.Second,
KeepAlive: 30 * time.Second, KeepAlive: 30 * time.Second,
// DualStack: true,
Resolver: c.bootstrapResolver, Resolver: c.bootstrapResolver,
} }
c.httpTransport = &http.Transport{ c.httpTransport = &http.Transport{
DialContext: dialer.DialContext, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if c.conf.Other.Interface == "" {
return baseDialer.DialContext(ctx, network, addr)
}
if network == "tcp4" && localV4 != nil {
d := *baseDialer
d.LocalAddr = &net.TCPAddr{IP: localV4}
return d.DialContext(ctx, network, addr)
}
if network == "tcp6" && localV6 != nil {
d := *baseDialer
d.LocalAddr = &net.TCPAddr{IP: localV6}
return d.DialContext(ctx, network, addr)
}
// Manual Dual-Stack: Resolve host and try compatible families sequentially
host, port, _ := net.SplitHostPort(addr)
ips, err := c.bootstrapResolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
var lastErr error
for _, ip := range ips {
d := *baseDialer
targetAddr := net.JoinHostPort(ip.String(), port)
if ip.IP.To4() != nil {
if localV4 == nil {
continue
}
d.LocalAddr = &net.TCPAddr{IP: localV4}
} else {
if localV6 == nil {
continue
}
d.LocalAddr = &net.TCPAddr{IP: localV6}
}
conn, err := d.DialContext(ctx, "tcp", targetAddr)
if err == nil {
return conn, nil
}
lastErr = err
}
if lastErr != nil {
return nil, lastErr
}
return nil, fmt.Errorf("connection to %s failed: no matching local/remote IP families on interface %s", addr, c.conf.Other.Interface)
},
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second, IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100, MaxIdleConns: 100,
MaxIdleConnsPerHost: 10, MaxIdleConnsPerHost: 10,
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: time.Duration(c.conf.Other.Timeout) * time.Second, TLSHandshakeTimeout: time.Duration(c.conf.Other.Timeout) * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.conf.Other.TLSInsecureSkipVerify},
} }
if c.conf.Other.NoIPv6 { if c.conf.Other.NoIPv6 {
originalDial := c.httpTransport.DialContext
c.httpTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { c.httpTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
if strings.HasPrefix(network, "tcp") { if strings.HasPrefix(network, "tcp") {
network = "tcp4" network = "tcp4"
} }
return dialer.DialContext(ctx, network, address) return originalDial(ctx, network, address)
} }
} }
err := http2.ConfigureTransport(c.httpTransport)
err = http2.ConfigureTransport(c.httpTransport)
if err != nil { if err != nil {
return err return err
} }
@@ -305,7 +424,7 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
if len(r.Question) != 1 { if len(r.Question) != 1 {
log.Println("Number of questions is not 1") log.Println("Number of questions is not 1")
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeFormatError reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply) w.WriteMsg(reply)
return return
@@ -356,7 +475,7 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
return return
} }
log.Println(err) log.Println(err)
reply = jsonDNS.PrepareReply(r) reply = jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return return
@@ -425,7 +544,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 // 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 // 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 { /*if req.response.StatusCode/100 == 5 {
c.selector.ReportUpstreamStatus(upstream, selector.Medium) c.selector.ReportUpstreamStatus(upstream, selector.Medium)
}*/ }*/
@@ -471,7 +590,7 @@ func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddre
if err != nil { if err != nil {
return return
} }
if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) { if ip := remoteAddr.IP; jsondns.IsGlobalIP(ip) {
if ipv4 := ip.To4(); ipv4 != nil { if ipv4 := ip.To4(); ipv4 != nil {
ednsClientAddress = ipv4.Mask(ipv4Mask24) ednsClientAddress = ipv4.Mask(ipv4Mask24)
ednsClientNetmask = 24 ednsClientNetmask = 24
@@ -482,3 +601,38 @@ func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddre
} }
return return
} }
// getInterfaceIPs returns the first valid IPv4 and IPv6 addresses found on the interface
func (c *Client) getInterfaceIPs() (v4, v6 net.IP, err error) {
if c.conf.Other.Interface == "" {
return nil, nil, nil
}
ifi, err := net.InterfaceByName(c.conf.Other.Interface)
if err != nil {
return nil, nil, err
}
addrs, err := ifi.Addrs()
if err != nil {
return nil, nil, err
}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
if ip4 := ip.To4(); ip4 != nil {
if v4 == nil {
v4 = ip4
}
} else {
if v6 == nil && !c.conf.Other.NoIPv6 {
v6 = ip
}
}
}
if v4 == nil && v6 == nil {
return nil, nil, fmt.Errorf("no valid IP addresses found on interface %s", c.conf.Other.Interface)
}
return v4, v6, nil
}

View File

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

View File

@@ -64,7 +64,7 @@ upstream_selector = "random"
# bootstrap server, please make this list empty. # bootstrap server, please make this list empty.
bootstrap = [ bootstrap = [
# Google's resolver, bad ECS, good DNSSEC # Google's resolver, good ECS, good DNSSEC
"8.8.8.8:53", "8.8.8.8:53",
"8.8.4.4:53", "8.8.4.4:53",
@@ -81,7 +81,9 @@ passthrough = [
"captive.apple.com", "captive.apple.com",
"connectivitycheck.gstatic.com", "connectivitycheck.gstatic.com",
"detectportal.firefox.com", "detectportal.firefox.com",
"globalreachtech.com",
"msftconnecttest.com", "msftconnecttest.com",
"network-auth.com",
"nmcheck.gnome.org", "nmcheck.gnome.org",
"pool.ntp.org", "pool.ntp.org",
@@ -95,6 +97,11 @@ passthrough = [
# Timeout for upstream request in seconds # Timeout for upstream request in seconds
timeout = 30 timeout = 30
# Interface to bind to for outgoing connections.
# If empty, the system default route is used (usually eth0 or wlan0).
# Example: "eth1", "wlan0"
interface = ""
# Disable HTTP Cookies # Disable HTTP Cookies
# #
# Cookies may be useful if your upstream resolver is protected by some # Cookies may be useful if your upstream resolver is protected by some
@@ -132,3 +139,9 @@ no_user_agent = false
# Enable logging # Enable logging
verbose = false 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" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"github.com/m13253/dns-over-https/doh-client/selector"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/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 { 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 questionName := question.Name
questionClass := question.Qclass questionClass := question.Qclass
if questionClass != dns.ClassINET { if questionClass != dns.ClassINET {
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeRefused reply.Rcode = dns.RcodeRefused
w.WriteMsg(reply) w.WriteMsg(reply)
return &DNSRequest{ return &DNSRequest{
@@ -77,10 +78,10 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
requestURL += fmt.Sprintf("&edns_client_subnet=%s/%d", ednsClientAddress.String(), ednsClientNetmask) 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 { if err != nil {
log.Println(err) log.Println(err)
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return &DNSRequest{ return &DNSRequest{
@@ -111,7 +112,7 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return &DNSRequest{ return &DNSRequest{
@@ -121,7 +122,7 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
return &DNSRequest{ return &DNSRequest{
response: resp, response: resp,
reply: jsonDNS.PrepareReply(r), reply: jsondns.PrepareReply(r),
udpSize: udpSize, udpSize: udpSize,
ednsClientAddress: ednsClientAddress, ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask, ednsClientNetmask: ednsClientNetmask,
@@ -140,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 { if err != nil {
log.Println(err) log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
@@ -148,7 +149,7 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
return return
} }
var respJSON jsonDNS.Response var respJSON jsondns.Response
err = json.Unmarshal(body, &respJSON) err = json.Unmarshal(body, &respJSON)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@@ -162,7 +163,12 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
} }
fixEmptyNames(&respJSON) 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() buf, err := fullReply.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@@ -170,22 +176,13 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
w.WriteMsg(req.reply) w.WriteMsg(req.reply)
return 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) w.Write(buf)
} }
// Fix DNS response empty []RR.Name // Fix DNS response empty []RR.Name
// Additional section won't be rectified // Additional section won't be rectified
// see: https://stackoverflow.com/questions/52136176/what-is-additional-section-in-dns-and-how-it-works // see: https://stackoverflow.com/questions/52136176/what-is-additional-section-in-dns-and-how-it-works
func fixEmptyNames(respJSON *jsonDNS.Response) { func fixEmptyNames(respJSON *jsondns.Response) {
for i := range respJSON.Answer { for i := range respJSON.Answer {
if respJSON.Answer[i].Name == "" { if respJSON.Answer[i].Name == "" {
respJSON.Answer[i].Name = "." respJSON.Answer[i].Name = "."

View File

@@ -28,16 +28,17 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"time" "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/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 { 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() requestBinary, err := r.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeFormatError reply.Rcode = dns.RcodeFormatError
w.WriteMsg(reply) w.WriteMsg(reply)
return &DNSRequest{ return &DNSRequest{
@@ -104,10 +105,10 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
var req *http.Request var req *http.Request
if len(requestURL) < 2048 { if len(requestURL) < 2048 {
req, err = http.NewRequest(http.MethodGet, requestURL, nil) req, err = http.NewRequest(http.MethodGet, requestURL, http.NoBody)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return &DNSRequest{ 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)) req, err = http.NewRequest(http.MethodPost, upstream.URL, bytes.NewReader(requestBinary))
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return &DNSRequest{ return &DNSRequest{
@@ -149,7 +150,7 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
if err != nil { if err != nil {
log.Println(err) log.Println(err)
reply := jsonDNS.PrepareReply(r) reply := jsondns.PrepareReply(r)
reply.Rcode = dns.RcodeServerFailure reply.Rcode = dns.RcodeServerFailure
w.WriteMsg(reply) w.WriteMsg(reply)
return &DNSRequest{ return &DNSRequest{
@@ -159,7 +160,7 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
return &DNSRequest{ return &DNSRequest{
response: resp, response: resp,
reply: jsonDNS.PrepareReply(r), reply: jsondns.PrepareReply(r),
udpSize: udpSize, udpSize: udpSize,
ednsClientAddress: ednsClientAddress, ednsClientAddress: ednsClientAddress,
ednsClientNetmask: ednsClientNetmask, 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 { if err != nil {
log.Printf("read error from upstream %s: %v\n", req.currentUpstream, err) log.Printf("read error from upstream %s: %v\n", req.currentUpstream, err)
req.reply.Rcode = dns.RcodeServerFailure req.reply.Rcode = dns.RcodeServerFailure
@@ -231,6 +232,11 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
_ = fixRecordTTL(rr, timeDelta) _ = fixRecordTTL(rr, timeDelta)
} }
if isTCP {
fullReply.Truncate(dns.MaxMsgSize)
} else {
fullReply.Truncate(int(req.udpSize))
}
buf, err := fullReply.Pack() buf, err := fullReply.Pack()
if err != nil { if err != nil {
log.Printf("packing error with upstream %s: %v\n", req.currentUpstream, err) 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) w.WriteMsg(req.reply)
return 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) _, err = w.Write(buf)
if err != nil { if err != nil {
log.Printf("failed to write to client: %v\n", err) log.Printf("failed to write to client: %v\n", err)

View File

@@ -26,21 +26,19 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil"
"log" "log"
"os" "os"
"runtime" "runtime"
"strconv" "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) { func checkPIDFile(pidFile string) (bool, error) {
retry: 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) { if os.IsExist(err) {
pidStr, err := ioutil.ReadFile(pidFile) pidStr, err := os.ReadFile(pidFile)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -64,7 +62,7 @@ retry:
return false, err return false, err
} }
defer f.Close() 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 { if err != nil {
return false, err return false, err
} }

View File

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

View File

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

View File

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

View File

@@ -3,12 +3,12 @@ package selector
type upstreamStatus int type upstreamStatus int
const ( 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 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 Error
// when query upstream ok, means upstream is available // when query upstream ok, means upstream is available.
OK OK
) )

View File

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

View File

@@ -31,17 +31,21 @@ import (
) )
type config struct { type config struct {
Listen []string `toml:"listen"` TLSClientAuthCA string `toml:"tls_client_auth_ca"`
LocalAddr string `toml:"local_addr"` LocalAddr string `toml:"local_addr"`
Cert string `toml:"cert"` Cert string `toml:"cert"`
Key string `toml:"key"` Key string `toml:"key"`
Path string `toml:"path"` Path string `toml:"path"`
DebugHTTPHeaders []string `toml:"debug_http_headers"`
Listen []string `toml:"listen"`
Upstream []string `toml:"upstream"` Upstream []string `toml:"upstream"`
Timeout uint `toml:"timeout"` Timeout uint `toml:"timeout"`
Tries uint `toml:"tries"` Tries uint `toml:"tries"`
Verbose bool `toml:"verbose"` Verbose bool `toml:"verbose"`
DebugHTTPHeaders []string `toml:"debug_http_headers"`
LogGuessedIP bool `toml:"log_guessed_client_ip"` 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) { 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 # 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 # Note: http uri/useragent log cannot be controlled by this config
log_guessed_client_ip = false 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" "strings"
"time" "time"
"github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/idna" "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 { func (s *Server) parseRequestGoogle(ctx context.Context, w http.ResponseWriter, r *http.Request) *DNSRequest {
@@ -90,46 +91,15 @@ func (s *Server) parseRequestGoogle(ctx context.Context, w http.ResponseWriter,
if ednsClientSubnet == "0/0" { if ednsClientSubnet == "0/0" {
ednsClientSubnet = "0.0.0.0/0" ednsClientSubnet = "0.0.0.0/0"
} }
slash := strings.IndexByte(ednsClientSubnet, '/')
if slash < 0 { var err error
ednsClientAddress = net.ParseIP(ednsClientSubnet) ednsClientFamily, ednsClientAddress, ednsClientNetmask, err = parseSubnet(ednsClientSubnet)
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
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 { if err != nil {
return &DNSRequest{ return &DNSRequest{
errcode: 400, errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"edns_client_subnet\" = %q", ednsClientSubnet), errtext: err.Error(),
} }
} }
ednsClientNetmask = uint8(netmask)
}
} else { } else {
ednsClientAddress = s.findClientIP(r) ednsClientAddress = s.findClientIP(r)
if ednsClientAddress == nil { if ednsClientAddress == nil {
@@ -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) { 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) respStr, err := json.Marshal(respJSON)
if err != nil { if err != nil {
log.Println(err) 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 return
} }

View File

@@ -28,7 +28,7 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
@@ -36,8 +36,9 @@ import (
"strings" "strings"
"time" "time"
jsonDNS "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/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 { 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") { 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 { if err != nil {
return &DNSRequest{ return &DNSRequest{
errcode: 400, errcode: 400,
@@ -61,7 +62,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
if len(requestBinary) == 0 { if len(requestBinary) == 0 {
return &DNSRequest{ return &DNSRequest{
errcode: 400, 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 isTailored := edns0Subnet == nil
if edns0Subnet == nil { if edns0Subnet == nil {
ednsClientFamily := uint16(0) ednsClientFamily := uint16(0)
ednsClientAddress := s.findClientIP(r) 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 { if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1 ednsClientFamily = 1
ednsClientAddress = ipv4 ednsClientAddress = ipv4
if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 32
} else {
ednsClientNetmask = 24 ednsClientNetmask = 24
ednsClientAddress = ednsClientAddress.Mask(net.CIDRMask(24, 32))
}
} else { } else {
ednsClientFamily = 2 ednsClientFamily = 2
if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 128
} else {
ednsClientNetmask = 56 ednsClientNetmask = 56
ednsClientAddress = ednsClientAddress.Mask(net.CIDRMask(56, 128))
}
} }
edns0Subnet = new(dns.EDNS0_SUBNET) edns0Subnet = new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET 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) { 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 req.response.Id = req.transactionID
respBytes, err := req.response.Pack() respBytes, err := req.response.Pack()
if err != nil { if err != nil {
log.Printf("DNS packet construct failure with upstream %s: %v\n", req.currentUpstream, err) 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 return
} }
@@ -171,8 +183,6 @@ func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter
w.Header().Set("Last-Modified", now) w.Header().Set("Last-Modified", now)
w.Header().Set("Vary", "Accept") w.Header().Set("Vary", "Accept")
_ = s.patchFirefoxContentType(w, r, req)
if respJSON.HaveTTL { if respJSON.HaveTTL {
if req.isTailored { if req.isTailored {
w.Header().Set("Cache-Control", "private, max-age="+strconv.FormatUint(uint64(respJSON.LeastTTL), 10)) 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 { 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")) { 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")) {
if s.conf.Verbose {
log.Println("DNSCrypt-Proxy detected. Patching response.") log.Println("DNSCrypt-Proxy detected. Patching response.")
}
w.Header().Set("Content-Type", "application/dns-message") w.Header().Set("Content-Type", "application/dns-message")
w.Header().Set("Vary", "Accept, User-Agent") w.Header().Set("Vary", "Accept, User-Agent")
now := time.Now().UTC().Format(http.TimeFormat) now := time.Now().UTC().Format(http.TimeFormat)
@@ -205,15 +217,3 @@ func (s *Server) patchDNSCryptProxyReqID(w http.ResponseWriter, r *http.Request,
} }
return false 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 ( import (
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil"
"log" "log"
"os" "os"
"runtime" "runtime"
@@ -36,9 +34,9 @@ import (
func checkPIDFile(pidFile string) (bool, error) { func checkPIDFile(pidFile string) (bool, error) {
retry: 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) { if os.IsExist(err) {
pidStr, err := ioutil.ReadFile(pidFile) pidStr, err := os.ReadFile(pidFile)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -62,7 +60,7 @@ retry:
return false, err return false, err
} }
defer f.Close() 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 { if err != nil {
return false, err 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 ( import (
"context" "context"
"crypto/tls"
"crypto/x509"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
@@ -35,8 +37,9 @@ import (
"time" "time"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
jsonDNS "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
) )
type Server struct { type Server struct {
@@ -50,11 +53,11 @@ type Server struct {
type DNSRequest struct { type DNSRequest struct {
request *dns.Msg request *dns.Msg
response *dns.Msg response *dns.Msg
transactionID uint16
currentUpstream string currentUpstream string
isTailored bool
errcode int
errtext string errtext string
errcode int
transactionID uint16
isTailored bool
} }
func NewServer(conf *config) (*Server, error) { func NewServer(conf *config) (*Server, error) {
@@ -107,12 +110,48 @@ func (s *Server) Start() error {
if s.conf.Verbose { if s.conf.Verbose {
servemux = handlers.CombinedLoggingHandler(os.Stdout, servemux) 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)) results := make(chan error, len(s.conf.Listen))
for _, addr := range s.conf.Listen { for _, addr := range s.conf.Listen {
go func(addr string) { go func(addr string) {
var err error var err error
if s.conf.Cert != "" || s.conf.Key != "" { if s.conf.Cert != "" || s.conf.Key != "" {
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) err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux)
}
} else { } else {
err = http.ListenAndServe(addr, servemux) err = http.ListenAndServe(addr, servemux)
} }
@@ -216,23 +255,22 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
} else if contentType == "application/dns-udpwireformat" { } else if contentType == "application/dns-udpwireformat" {
req = s.parseRequestIETF(ctx, w, r) req = s.parseRequestIETF(ctx, w, r)
} else { } 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 return
} }
if req.errcode == 444 { if req.errcode == 444 {
return return
} }
if req.errcode != 0 { if req.errcode != 0 {
jsonDNS.FormatError(w, req.errtext, req.errcode) jsondns.FormatError(w, req.errtext, req.errcode)
return return
} }
req = s.patchRootRD(req) req = s.patchRootRD(req)
var err error err := s.doDNSQuery(ctx, req)
req, err = s.doDNSQuery(ctx, req)
if err != nil { 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 return
} }
@@ -247,7 +285,7 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
func (s *Server) findClientIP(r *http.Request) net.IP { func (s *Server) findClientIP(r *http.Request) net.IP {
noEcs := r.URL.Query().Get("no_ecs") noEcs := r.URL.Query().Get("no_ecs")
if strings.ToLower(noEcs) == "true" { if strings.EqualFold(noEcs, "true") {
return nil return nil
} }
@@ -256,7 +294,7 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
for _, addr := range strings.Split(XForwardedFor, ",") { for _, addr := range strings.Split(XForwardedFor, ",") {
addr = strings.TrimSpace(addr) addr = strings.TrimSpace(addr)
ip := net.ParseIP(addr) ip := net.ParseIP(addr)
if jsonDNS.IsGlobalIP(ip) { if jsondns.IsGlobalIP(ip) {
return ip return ip
} }
} }
@@ -265,21 +303,23 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
if XRealIP != "" { if XRealIP != "" {
addr := strings.TrimSpace(XRealIP) addr := strings.TrimSpace(XRealIP)
ip := net.ParseIP(addr) ip := net.ParseIP(addr)
if jsonDNS.IsGlobalIP(ip) { if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) {
return ip return ip
} }
} }
remoteAddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr) remoteAddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err != nil { if err != nil {
return nil return nil
} }
if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) { ip := remoteAddr.IP
if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) {
return ip return ip
} }
return nil 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 { func (s *Server) patchRootRD(req *DNSRequest) *DNSRequest {
for _, question := range req.request.Question { for _, question := range req.request.Question {
if question.Name == "." { if question.Name == "." {
@@ -289,7 +329,7 @@ func (s *Server) patchRootRD(req *DNSRequest) *DNSRequest {
return req 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 { func (s *Server) indexQuestionType(msg *dns.Msg, qtype uint16) int {
for i, question := range msg.Question { for i, question := range msg.Question {
if question.Qtype == qtype { if question.Qtype == qtype {
@@ -299,7 +339,7 @@ func (s *Server) indexQuestionType(msg *dns.Msg, qtype uint16) int {
return -1 return -1
} }
func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequest, err error) { func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (err error) {
numServers := len(s.conf.Upstream) numServers := len(s.conf.Upstream)
for i := uint(0); i < s.conf.Tries; i++ { for i := uint(0); i < s.conf.Tries; i++ {
req.currentUpstream = s.conf.Upstream[rand.Intn(numServers)] req.currentUpstream = s.conf.Upstream[rand.Intn(numServers)]
@@ -309,7 +349,7 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
switch t { switch t {
default: default:
log.Printf("invalid DNS type %q in upstream %q", t, upstream) 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 // Use DNS-over-TLS (DoT) if configured to do so
case "tcp-tls": case "tcp-tls":
req.response, _, err = s.tcpClientTLS.ExchangeContext(ctx, req.request, upstream) req.response, _, err = s.tcpClientTLS.ExchangeContext(ctx, req.request, upstream)
@@ -324,7 +364,7 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
req.response, _, err = s.tcpClient.ExchangeContext(ctx, 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) && if (s.indexQuestionType(req.request, dns.TypeIXFR) > -1) &&
(len(req.response.Answer) == 1) && (len(req.response.Answer) == 1) &&
(req.response.Answer[0].Header().Rrtype == dns.TypeSOA) { (req.response.Answer[0].Header().Rrtype == dns.TypeSOA) {
@@ -334,9 +374,9 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
} }
if err == nil { if err == nil {
return req, nil return nil
} }
log.Printf("DNS error from upstream %s: %s\n", req.currentUpstream, err.Error()) 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 package main
const ( const (
VERSION = "2.2.2" VERSION = "2.3.10"
USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)" USER_AGENT = "DNS-over-HTTPS/" + VERSION + " (+https://github.com/m13253/dns-over-https)"
) )

25
go.mod
View File

@@ -1,13 +1,20 @@
module github.com/m13253/dns-over-https module github.com/m13253/dns-over-https/v2
go 1.12 go 1.24.0
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v1.6.0
github.com/gorilla/handlers v1.4.0 github.com/gorilla/handlers v1.5.2
github.com/miekg/dns v1.1.31 github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect github.com/miekg/dns v1.1.70
golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/net v0.49.0
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 // indirect )
golang.org/x/text v0.3.2 // indirect
require (
github.com/felixge/httpsnoop v1.0.4 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/tools v0.41.0 // indirect
) )

85
go.sum
View File

@@ -1,55 +1,30 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/miekg/dns v1.1.22 h1:Jm64b3bO9kP43ddLjL2EY3Io6bmy1qGb9Xxz6TqS6rc= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo= github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc h1:RhT2pjLo3EVRmldbEcBdeRA7CGPWsNEJC+Y/N1aXQbg=
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a h1:Yu34BogBivvmu7SAzHHaB9nZWH5D1C+z3F1jyIaYZSQ= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/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/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

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

View File

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

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. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsondns
import ( import (
"net" "net"

View File

@@ -21,7 +21,7 @@
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsondns
import ( import (
"encoding/json" "encoding/json"
@@ -30,12 +30,12 @@ import (
type QuestionList []Question type QuestionList []Question
func (ql *QuestionList) UnmarshalJSON(b []byte) error {
// Fix variant question response in Response.Question // Fix variant question response in Response.Question
// //
// Solution taken from: // Solution taken from:
// https://engineering.bitnami.com/articles/dealing-with-json-with-non-homogeneous-types-in-go.html // https://engineering.bitnami.com/articles/dealing-with-json-with-non-homogeneous-types-in-go.html
// https://archive.is/NU4zR // https://archive.is/NU4zR
func (ql *QuestionList) UnmarshalJSON(b []byte) error {
if len(b) > 0 && b[0] == '[' { if len(b) > 0 && b[0] == '[' {
return json.Unmarshal(b, (*[]Question)(ql)) return json.Unmarshal(b, (*[]Question)(ql))
} }

View File

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

View File

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

View File

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