Compare commits

...

101 Commits

Author SHA1 Message Date
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
27 changed files with 516 additions and 244 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.21
id: go
- name: Check out repository
uses: actions/checkout@v2
- 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@v2
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') }}

View File

@@ -4,6 +4,35 @@ This Changelog records major changes between versions.
Not all changes are recorded. Please check git log for details.
## Version 2.3.3
- systemd: Use DynamicUser=yes instead of User=nobody (Fixed #139)
- Migrate deprecated Go packages `ioutil` to `io` and `os`
- Fix a bug that truncates the response improperly, causing malformed DNS responsed (Fixed #144)
## Version 2.3.2
- Documentation updates, including deploying recommenation alongside DoT, thanks @gdm85
- Add unit tests for CIDR subnets parsing, thanks @gdm85
- Removing Firefox 61-62 patch
Since this version, @gdm85, @GreyXor, @Jamesits will be able to maintain this repository alongside @m13253. Anyone who contributed to this project can also apply to be a maintainer.
This is because changes in life have delayed the development of this project. By constructing a community hopefully can we restore the pace of development.
## Version 2.3.1
- No new features in this release
- Bumped versions of Go toolchain and third-party dependencies, requested by #128
## Version 2.3.0
- The repository now conforms to the Go semvar standard (Fixed #115, thanks to @leiless)
## Version 2.2.5
- Add client certificate authentication
- Fixing documentation related to Docker
## Version 2.2.4
- Add options to configure ECS netmask length

View File

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

View File

@@ -11,8 +11,8 @@ and [IETF DNS-over-HTTPS (RFC 8484)](https://www.rfc-editor.org/rfc/rfc8484.txt)
## Installing
### From Source
- Install [Go](https://golang.org), at least version 1.10.
> Note for Debian/Ubuntu users: You need to set `$GOROOT` if you could not get your new version of Go selected by the Makefile.)
- 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.
- First create an empty directory, used for `$GOPATH`:
```bash
@@ -56,16 +56,26 @@ sudo make uninstall
### Using docker image
```bash
docker run -itd --name doh-server \
docker run -d --name doh-server \
-p 8053:8053 \
-e UPSTREAM_DNS_SERVER="udp:8.8.8.8:53" \
-e DOH_HTTP_PREFIX="/dns-query"
-e DOH_SERVER_LISTEN=":8053"
-e DOH_SERVER_TIMEOUT="10"
-e DOH_SERVER_TRIES="3"
-e DOH_SERVER_VERBOSE="false"
satishweb/doh-server
-e UPSTREAM_DNS_SERVER="udp:208.67.222.222:53,udp:208.67.220.220:53" \
-e DOH_HTTP_PREFIX="/dns-query" \
-e DOH_SERVER_LISTEN=":8053" \
-e DOH_SERVER_TIMEOUT="10" \
-e DOH_SERVER_TRIES="3" \
-e DOH_SERVER_VERBOSE="false" \
satishweb/doh-server
```
Note: Multiple Upstream DNS server support was added in the container image on 2024-12-19.
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
@@ -84,7 +94,7 @@ The following is a typical DNS-over-HTTPS architecture:
| doh-client +--+ Content Delivery Network +--+ (Apache, Nginx, Caddy) |
+--------------+ +--------------------------+ +------------------------+
Although DNS-over-HTTPS can work alone, a HTTP service muxer would be useful as
Although DNS-over-HTTPS can work alone, an HTTP service muxer would be useful as
you can host DNS-over-HTTPS along with other HTTPS services.
HTTP/2 with at least TLS v1.3 is recommended. OCSP stapling must be enabled,
@@ -127,7 +137,7 @@ server {
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_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
@@ -170,10 +180,11 @@ my.server.name {
version: '2.2'
networks:
default:
services:
proxy:
# The official v2 Traefik docker image
image: traefik:v2.2
image: traefik:v2.3
hostname: proxy
networks:
- default
@@ -266,14 +277,30 @@ services:
> 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
### Example configuration: DNS-over-TLS
There is no native [DNS-over-TLS](https://en.wikipedia.org/wiki/DNS_over_TLS) support, but you can easily add it via nginx:
```
stream {
server {
listen *:853 ssl;
proxy_pass ipofyourdnsresolver:port #127.0.0.1:53
}
ssl_certificate /etc/letsencrypt/live/site.yourdomain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/site.yourdomain/privkey.pem;
}
```
The DoT service can also be provided by running a [STunnel](https://www.stunnel.org/) instance to wrap dnsmasq (or any other resolver of your choice, listening on a TCP port);
this approach does not need a stand-alone daemon to provide the DoT service.
## DNSSEC
DNS-over-HTTPS is compatible with DNSSEC, and requests DNSSEC signatures by
default. However signature validation is not built-in. It is highly recommended
default. However, signature validation is not built-in. It is highly recommended
that you install `unbound` or `bind` and pass results for them to validate DNS
records. 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-Client-Subnet (/24 for IPv4, /56 for IPv6 by default)
## Known issues
* it does not work well with [dnscrypt-proxy](https://github.com/DNSCrypt/dnscrypt-proxy), you might want to use either (or fix the compatibility bugs by submitting PRs)
## The name of the project
This project is named "DNS-over-HTTPS" because it was written before the IETF DoH project. Although this project is compatible with IETF DoH, the project is not affiliated with IETF.

View File

@@ -38,39 +38,40 @@ import (
"sync"
"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"
"golang.org/x/net/http2"
"golang.org/x/net/idna"
"github.com/m13253/dns-over-https/v2/doh-client/config"
"github.com/m13253/dns-over-https/v2/doh-client/selector"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
type Client struct {
conf *config.Config
bootstrap []string
passthrough []string
udpClient *dns.Client
tcpClient *dns.Client
udpServers []*dns.Server
tcpServers []*dns.Server
bootstrapResolver *net.Resolver
httpClientLastCreate time.Time
cookieJar http.CookieJar
selector selector.Selector
httpClientMux *sync.RWMutex
tcpClient *dns.Client
bootstrapResolver *net.Resolver
udpClient *dns.Client
conf *config.Config
httpTransport *http.Transport
httpClient *http.Client
httpClientLastCreate time.Time
selector selector.Selector
udpServers []*dns.Server
tcpServers []*dns.Server
passthrough []string
bootstrap []string
}
type DNSRequest struct {
err error
response *http.Response
reply *dns.Msg
udpSize uint16
ednsClientAddress net.IP
ednsClientNetmask uint8
currentUpstream string
err error
ednsClientAddress net.IP
udpSize uint16
ednsClientNetmask uint8
}
func NewClient(conf *config.Config) (c *Client, err error) {
@@ -427,7 +428,7 @@ func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
// https://developers.cloudflare.com/1.1.1.1/dns-over-https/request-structure/ says
// returns code will be 200 / 400 / 413 / 415 / 504, some server will return 503, so
// I think if status code is 5xx, upstream must has some problems
// I think if status code is 5xx, upstream must have some problems
/*if req.response.StatusCode/100 == 5 {
c.selector.ReportUpstreamStatus(upstream, selector.Medium)
}*/

View File

@@ -27,16 +27,17 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/m13253/dns-over-https/doh-client/selector"
jsondns "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
"github.com/m13253/dns-over-https/v2/doh-client/selector"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter, r *dns.Msg, isTCP bool, upstream *selector.Upstream) *DNSRequest {
@@ -77,7 +78,7 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
requestURL += fmt.Sprintf("&edns_client_subnet=%s/%d", ednsClientAddress.String(), ednsClientNetmask)
}
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
req, err := http.NewRequest(http.MethodGet, requestURL, http.NoBody)
if err != nil {
log.Println(err)
reply := jsondns.PrepareReply(r)
@@ -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 {
log.Println(err)
req.reply.Rcode = dns.RcodeServerFailure
@@ -163,6 +164,11 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
fixEmptyNames(&respJSON)
fullReply := jsondns.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask)
if isTCP {
fullReply.Truncate(dns.MaxMsgSize)
} else {
fullReply.Truncate(int(req.udpSize))
}
buf, err := fullReply.Pack()
if err != nil {
log.Println(err)
@@ -170,15 +176,6 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
w.WriteMsg(req.reply)
return
}
if !isTCP && len(buf) > int(req.udpSize) {
fullReply.Truncated = true
buf, err = fullReply.Pack()
if err != nil {
log.Println(err)
return
}
buf = buf[:req.udpSize]
}
w.Write(buf)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -57,9 +57,10 @@ log_guessed_client_ip = false
# 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, change the following
# option to "true".
# 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
@@ -69,3 +70,9 @@ ecs_allow_non_global_ip = false
# internet where IP address may be used to identify the user and
# not only the approximate location.
ecs_use_precise_ip = false
# If DOH is used for a controlled network, it is possible to enable
# the client TLS certificate validation with a specific certificate
# authority used to sign any client one. Disabled by default.
# tls_client_auth = true
# tls_client_auth_ca = "root-ca-public.crt"

View File

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

View File

@@ -28,7 +28,7 @@ import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
@@ -36,8 +36,9 @@ import (
"strings"
"time"
jsondns "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r *http.Request) *DNSRequest {
@@ -50,7 +51,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
}
}
if len(requestBinary) == 0 && (r.Header.Get("Content-Type") == "application/dns-message" || r.Header.Get("Content-Type") == "application/dns-udpwireformat") {
requestBinary, err = ioutil.ReadAll(r.Body)
requestBinary, err = io.ReadAll(r.Body)
if err != nil {
return &DNSRequest{
errcode: 400,
@@ -61,7 +62,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
if len(requestBinary) == 0 {
return &DNSRequest{
errcode: 400,
errtext: fmt.Sprintf("Invalid argument value: \"dns\""),
errtext: "Invalid argument value: \"dns\"",
}
}
@@ -134,7 +135,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
if ipv4 := ednsClientAddress.To4(); ipv4 != nil {
ednsClientFamily = 1
ednsClientAddress = ipv4
if s.conf.ECSFullSubnet {
if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 32
} else {
ednsClientNetmask = 24
@@ -142,7 +143,7 @@ func (s *Server) parseRequestIETF(ctx context.Context, w http.ResponseWriter, r
}
} else {
ednsClientFamily = 2
if s.conf.ECSFullSubnet {
if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 128
} else {
ednsClientNetmask = 56
@@ -182,8 +183,6 @@ func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter
w.Header().Set("Last-Modified", now)
w.Header().Set("Vary", "Accept")
_ = s.patchFirefoxContentType(w, r, req)
if respJSON.HaveTTL {
if req.isTailored {
w.Header().Set("Cache-Control", "private, max-age="+strconv.FormatUint(uint64(respJSON.LeastTTL), 10))
@@ -203,10 +202,12 @@ func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter
}
}
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe.
func (s *Server) patchDNSCryptProxyReqID(w http.ResponseWriter, r *http.Request, requestBinary []byte) bool {
if strings.Contains(r.UserAgent(), "dnscrypt-proxy") && bytes.Equal(requestBinary, []byte("\xca\xfe\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x02\x00\x01\x00\x00\x29\x10\x00\x00\x00\x80\x00\x00\x00")) {
log.Println("DNSCrypt-Proxy detected. Patching response.")
if s.conf.Verbose {
log.Println("DNSCrypt-Proxy detected. Patching response.")
}
w.Header().Set("Content-Type", "application/dns-message")
w.Header().Set("Vary", "Accept, User-Agent")
now := time.Now().UTC().Format(http.TimeFormat)
@@ -216,15 +217,3 @@ func (s *Server) patchDNSCryptProxyReqID(w http.ResponseWriter, r *http.Request,
}
return false
}
// Workaround a bug causing Firefox 61-62 to reject responses with Content-Type = application/dns-message
func (s *Server) patchFirefoxContentType(w http.ResponseWriter, r *http.Request, req *DNSRequest) bool {
if strings.Contains(r.UserAgent(), "Firefox") && strings.Contains(r.Header.Get("Accept"), "application/dns-udpwireformat") && !strings.Contains(r.Header.Get("Accept"), "application/dns-message") {
log.Println("Firefox 61-62 detected. Patching response.")
w.Header().Set("Content-Type", "application/dns-udpwireformat")
w.Header().Set("Vary", "Accept, User-Agent")
req.isTailored = true
return true
}
return false
}

View File

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

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

@@ -0,0 +1,119 @@
/*
DNS-over-HTTPS
Copyright (C) 2017-2018 Star Brilliant <m13253@hotmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
package main
import (
"testing"
"github.com/miekg/dns"
)
func TestParseCIDR(t *testing.T) {
t.Parallel()
for _, ednsClientSubnet := range []string{
"2001:db8::/0",
"2001:db8::/56",
"2001:db8::/129",
"2001:db8::",
"127.0.0.1/0",
"127.0.0.1/24",
"127.0.0.1/33",
"127.0.0.1",
"::ffff:7f00:1/0",
"::ffff:7f00:1/120",
"::ffff:7f00:1",
"127.0.0.1/0",
"127.0.0.1/24",
"127.0.0.1",
} {
_, ip, ipNet, err := parseSubnet(ednsClientSubnet)
if err != nil {
t.Errorf("ecs:%s ip:[%v] ipNet:[%v] err:[%v]", ednsClientSubnet, ip, ipNet, err)
}
}
}
func TestParseInvalidCIDR(t *testing.T) {
t.Parallel()
for _, ip := range []string{
"test",
"test/0",
"test/24",
"test/34",
"test/56",
"test/129",
} {
_, _, _, err := parseSubnet(ip)
if err == nil {
t.Errorf("expected error for %q", ip)
}
}
}
func TestEdns0SubnetParseCIDR(t *testing.T) {
t.Parallel()
// init dns Msg
msg := new(dns.Msg)
msg.Id = dns.Id()
msg.SetQuestion(dns.Fqdn("example.com"), 1)
// init edns0Subnet
edns0Subnet := new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET
edns0Subnet.SourceScope = 0
// init opt
opt := new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
opt.SetUDPSize(dns.DefaultMsgSize)
opt.Option = append(opt.Option, edns0Subnet)
msg.Extra = append(msg.Extra, opt)
for _, subnet := range []string{"::ffff:7f00:1/120", "127.0.0.1/24"} {
var err error
edns0Subnet.Family, edns0Subnet.Address, edns0Subnet.SourceNetmask, err = parseSubnet(subnet)
if err != nil {
t.Error(err)
continue
}
t.Log(msg.Pack())
}
// ------127.0.0.1/24-----
// [143 29 1 0 0 1 0 0 0 0 0 1 7 101 120 97 109 112 108 101 3 99 111 109 0 0 1 0 1 0
// opt start 0 41 16 0 0 0 0 0 0 11
// subnet start 0 8 0 7 0 1 24 0
// client subnet start 127 0 0]
// -----::ffff:7f00:1/120----
// [111 113 1 0 0 1 0 0 0 0 0 1 7 101 120 97 109 112 108 101 3 99 111 109 0 0 1 0 1 0
// opt start 0 41 16 0 0 0 0 0 0 23
// subnet start 0 8 0 19 0 2 120 0
// client subnet start 0 0 0 0 0 0 0 0 0 0 255 255 127 0 0]
}

View File

@@ -25,6 +25,8 @@ package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"math/rand"
@@ -35,8 +37,9 @@ import (
"time"
"github.com/gorilla/handlers"
jsondns "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns"
jsondns "github.com/m13253/dns-over-https/v2/json-dns"
)
type Server struct {
@@ -50,11 +53,11 @@ type Server struct {
type DNSRequest struct {
request *dns.Msg
response *dns.Msg
transactionID uint16
currentUpstream string
isTailored bool
errcode int
errtext string
errcode int
transactionID uint16
isTailored bool
}
func NewServer(conf *config) (*Server, error) {
@@ -107,12 +110,48 @@ func (s *Server) Start() error {
if s.conf.Verbose {
servemux = handlers.CombinedLoggingHandler(os.Stdout, servemux)
}
var clientCAPool *x509.CertPool
if s.conf.TLSClientAuth {
if s.conf.TLSClientAuthCA != "" {
clientCA, err := os.ReadFile(s.conf.TLSClientAuthCA)
if err != nil {
log.Fatalf("Reading certificate for client authentication has failed: %v", err)
}
clientCAPool = x509.NewCertPool()
clientCAPool.AppendCertsFromPEM(clientCA)
log.Println("Certificate loaded for client TLS authentication")
} else {
log.Fatalln("TLS client authentication requires both tls_client_auth and tls_client_auth_ca, exiting.")
}
}
results := make(chan error, len(s.conf.Listen))
for _, addr := range s.conf.Listen {
go func(addr string) {
var err error
if s.conf.Cert != "" || s.conf.Key != "" {
err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux)
if clientCAPool != nil {
srvtls := &http.Server{
Handler: servemux,
Addr: addr,
TLSConfig: &tls.Config{
ClientCAs: clientCAPool,
ClientAuth: tls.RequireAndVerifyClientCert,
GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
c, err := tls.LoadX509KeyPair(s.conf.Cert, s.conf.Key)
if err != nil {
fmt.Printf("Error loading server certificate key pair: %v\n", err)
return nil, err
}
return &c, nil
},
},
}
err = srvtls.ListenAndServeTLS("", "")
} else {
err = http.ListenAndServeTLS(addr, s.conf.Cert, s.conf.Key, servemux)
}
} else {
err = http.ListenAndServe(addr, servemux)
}
@@ -229,8 +268,7 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
req = s.patchRootRD(req)
var err error
req, err = s.doDNSQuery(ctx, req)
err := s.doDNSQuery(ctx, req)
if err != nil {
jsondns.FormatError(w, fmt.Sprintf("DNS query failure (%s)", err.Error()), 503)
return
@@ -247,7 +285,7 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
func (s *Server) findClientIP(r *http.Request) net.IP {
noEcs := r.URL.Query().Get("no_ecs")
if strings.ToLower(noEcs) == "true" {
if strings.EqualFold(noEcs, "true") {
return nil
}
@@ -265,7 +303,7 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
if XRealIP != "" {
addr := strings.TrimSpace(XRealIP)
ip := net.ParseIP(addr)
if !s.conf.LocalIPFilter || jsondns.IsGlobalIP(ip) {
if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) {
return ip
}
}
@@ -274,17 +312,14 @@ func (s *Server) findClientIP(r *http.Request) net.IP {
if err != nil {
return nil
}
if !s.conf.LocalIPFilter {
return remoteAddr.IP
}
if ip := remoteAddr.IP; jsondns.IsGlobalIP(ip) {
ip := remoteAddr.IP
if s.conf.ECSAllowNonGlobalIP || jsondns.IsGlobalIP(ip) {
return ip
}
return nil
}
// Workaround a bug causing Unbound to refuse returning anything about the root
// Workaround a bug causing Unbound to refuse returning anything about the root.
func (s *Server) patchRootRD(req *DNSRequest) *DNSRequest {
for _, question := range req.request.Question {
if question.Name == "." {
@@ -294,7 +329,7 @@ func (s *Server) patchRootRD(req *DNSRequest) *DNSRequest {
return req
}
// Return the position index for the question of qtype from a DNS msg, otherwise return -1
// Return the position index for the question of qtype from a DNS msg, otherwise return -1.
func (s *Server) indexQuestionType(msg *dns.Msg, qtype uint16) int {
for i, question := range msg.Question {
if question.Qtype == qtype {
@@ -304,7 +339,7 @@ func (s *Server) indexQuestionType(msg *dns.Msg, qtype uint16) int {
return -1
}
func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequest, err error) {
func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (err error) {
numServers := len(s.conf.Upstream)
for i := uint(0); i < s.conf.Tries; i++ {
req.currentUpstream = s.conf.Upstream[rand.Intn(numServers)]
@@ -314,7 +349,7 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
switch t {
default:
log.Printf("invalid DNS type %q in upstream %q", t, upstream)
return nil, &configError{"invalid DNS type"}
return &configError{"invalid DNS type"}
// Use DNS-over-TLS (DoT) if configured to do so
case "tcp-tls":
req.response, _, err = s.tcpClientTLS.ExchangeContext(ctx, req.request, upstream)
@@ -329,7 +364,7 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
req.response, _, err = s.tcpClient.ExchangeContext(ctx, req.request, upstream)
}
// Retry with TCP if this was an IXFR request and we only received an SOA
// Retry with TCP if this was an IXFR request, and we only received an SOA
if (s.indexQuestionType(req.request, dns.TypeIXFR) > -1) &&
(len(req.response.Answer) == 1) &&
(req.response.Answer[0].Header().Rrtype == dns.TypeSOA) {
@@ -339,9 +374,9 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
}
if err == nil {
return req, nil
return nil
}
log.Printf("DNS error from upstream %s: %s\n", req.currentUpstream, err.Error())
}
return req, err
return err
}

View File

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

26
go.mod
View File

@@ -1,14 +1,20 @@
module github.com/m13253/dns-over-https
module github.com/m13253/dns-over-https/v2
go 1.12
go 1.23.5
require (
github.com/BurntSushi/toml v0.3.1
github.com/gorilla/handlers v1.4.0
github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9
github.com/miekg/dns v1.1.31
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 // indirect
golang.org/x/text v0.3.2 // indirect
github.com/BurntSushi/toml v1.4.0
github.com/gorilla/handlers v1.5.2
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc
github.com/miekg/dns v1.1.63
golang.org/x/net v0.34.0
)
require (
github.com/felixge/httpsnoop v1.0.4 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.29.0 // indirect
)

64
go.sum
View File

@@ -1,40 +1,32 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9 h1:w66aaP3c6SIQ0pi3QH1Tb4AMO3aWoEPxd1CNvLphbkA=
github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc h1:RhT2pjLo3EVRmldbEcBdeRA7CGPWsNEJC+Y/N1aXQbg=
github.com/infobloxopen/go-trees v0.0.0-20221216143356-66ceba885ebc/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
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-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -119,7 +119,6 @@ func init() {
IP: net.IP{0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Mask: net.IPMask{0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}, struct{}{})
}
func IsGlobalIP(ip net.IP) bool {

View File

@@ -12,7 +12,7 @@ LimitNOFILE=1048576
Restart=always
RestartSec=3
Type=simple
User=nobody
DynamicUser=yes
[Install]
WantedBy=multi-user.target

View File

@@ -10,7 +10,7 @@ LimitNOFILE=1048576
Restart=always
RestartSec=3
Type=simple
User=nobody
DynamicUser=yes
[Install]
WantedBy=multi-user.target