Compare commits

...

46 Commits

Author SHA1 Message Date
Star Brilliant
0a76416f8e Release 2.2.5 2021-03-25 14:30:44 +00:00
Star Brilliant
82c50163c1 Merge pull request #99 from amincheloh/patch-1
Fix not working example docker command
2021-01-26 19:24:34 +08:00
Amin Cheloh
d5c1c592f6 Fix not working example docker command 2021-01-25 14:30:26 +07:00
Alex Chauvin
1cf98e87c9 add client certificate authentication (#98)
* add client certificate authentication
* fix #97 for ECS forward local addresses
2021-01-08 08:34:25 +00:00
Satish Gaikwad
e7461f2d85 Documentation update: Docker example update (#96)
* Set traefik container version to 2.3 in docker-compose example. This supports recent lets encrypt changes.
* Remove docker swarm related references. Docker swarm example is no more valid. Docker compose is the best example available atm.
2020-12-27 13:25:25 +00:00
Star Brilliant
608394e2d2 Bump to version 2.2.5 2020-12-06 22:53:33 +00:00
Star Brilliant
eb166ececa Release 2.2.4 2020-12-06 22:52:35 +00:00
Star Brilliant
f557e4aa29 Reformat the code 2020-11-24 12:38:16 +00:00
Alex Chauvin
967faec56c add options for ECS full subnet mask in server & TLS verification bypass in client (#92)
* add ECS full size & limit filtering

* add tls certification bypass in configuration

* flush log lines

* changes following pull request comments

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

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

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

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

4
.gitignore vendored
View File

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

View File

@@ -4,6 +4,29 @@ 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.2.5
- Add client certificate authentication
- Fixing documentation related to Docker
## Version 2.2.4
- Add options to configure ECS netmask length
- Add an option to disable TLS verification (Note: dangerous)
## Version 2.2.3
- Use the library ipTree to determine whether an IP is global routable, improving the performance
- Google's 8.8.8.8 resolver is now marked as "Good ECS" in the example configuration file
## Version 2.2.2
- Allow client to opt-out EDNS0 Client Support
- [JSON-DoH] Honor DNSSEC OK flag for incoming DNS requests
- [JSON-DoH] Add support for non-standard response formats
- `X-Real-IP` is now used in logging if set by frontend load balancer
- Fix documentation
## Version 2.2.1 ## Version 2.2.1
- Fix messy log - Fix messy log

View File

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

329
Readme.md
View File

@@ -4,57 +4,68 @@ DNS-over-HTTPS
Client and server software to query DNS over HTTPS, using [Google DNS-over-HTTPS protocol](https://developers.google.com/speed/public-dns/docs/dns-over-https) Client and server software to query DNS over HTTPS, using [Google DNS-over-HTTPS protocol](https://developers.google.com/speed/public-dns/docs/dns-over-https)
and [IETF DNS-over-HTTPS (RFC 8484)](https://www.rfc-editor.org/rfc/rfc8484.txt). and [IETF DNS-over-HTTPS (RFC 8484)](https://www.rfc-editor.org/rfc/rfc8484.txt).
## Guide ## Guides
[Tutorial to setup your own DNS-over-HTTPS (DoH) server](https://www.aaflalo.me/2018/10/tutorial-setup-dns-over-https-server/). (Thanks to Antoine Aflalo) - [Tutorial: Setup your own DNS-over-HTTPS (DoH) server](https://www.aaflalo.me/2018/10/tutorial-setup-dns-over-https-server/). (Thanks to Antoine Aflalo)
- [Tutorial: Setup your own Docker based DNS-over-HTTPS (DoH) server](https://github.com/satishweb/docker-doh/blob/master/README.md). (Thanks to Satish Gaikwad)
## Installing ## 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.10. - First create an empty directory, used for `$GOPATH`:
```bash
(Note for Debian/Ubuntu users: You need to set `$GOROOT` if you could not get your new version of Go selected by the Makefile.) mkdir ~/gopath
export GOPATH=~/gopath
First create an empty directory, used for `$GOPATH`: ```
- To build the program, type:
mkdir ~/gopath ```bash
export GOPATH=~/gopath make
```
To build the program, type: - To install DNS-over-HTTPS as Systemd services, type:
```bash
make sudo make install
```
To install DNS-over-HTTPS as Systemd services, type: - By default, [Google DNS over HTTPS](https://dns.google.com) is used. It should
sudo make install
By default, [Google DNS over HTTPS](https://dns.google.com) is used. It should
work for most users (except for People's Republic of China). If you need to work for most users (except for People's Republic of China). If you need to
modify the default settings, type: modify the default settings, type:
```bash
sudoedit /etc/dns-over-https/doh-client.conf
```
- To automatically start DNS-over-HTTPS client as a system service, type:
```bash
sudo systemctl start doh-client.service
sudo systemctl enable doh-client.service
```
- Then, modify your DNS settings (usually with NetworkManager) to 127.0.0.1.
sudoedit /etc/dns-over-https/doh-client.conf - To test your configuration, type:
```bash
dig www.google.com
Output:
;; SERVER: 127.0.0.1#53(127.0.0.1)
```
#### Uninstall
To automatically start DNS-over-HTTPS client as a system service, type: - To uninstall, type:
```bash
sudo make uninstall
```
> Note: The configuration files are kept at `/etc/dns-over-https`. Remove them manually if you want.
sudo systemctl start doh-client.service ### Using docker image
sudo systemctl enable doh-client.service ```bash
docker run -itd --name doh-server \
Then, modify your DNS settings (usually with NetworkManager) to 127.0.0.1. -p 8053:8053 \
-e UPSTREAM_DNS_SERVER="udp:8.8.8.8:53" \
To test your configuration, type: -e DOH_HTTP_PREFIX="/dns-query" \
-e DOH_SERVER_LISTEN=":8053" \
dig www.google.com -e DOH_SERVER_TIMEOUT="10" \
-e DOH_SERVER_TRIES="3" \
If it is OK, you will see: -e DOH_SERVER_VERBOSE="false"
satishweb/doh-server
;; SERVER: 127.0.0.1#53(127.0.0.1) ```
### Uninstalling
To uninstall, type:
sudo make uninstall
The configuration files are kept at `/etc/dns-over-https`. Remove them manually if you want.
## Server Configuration ## Server Configuration
@@ -79,87 +90,191 @@ 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,
otherwise DNS recursion may happen. otherwise DNS recursion may happen.
### Configuration file
The main configuration file is `doh-client.conf`.
**Server selectors.** If several upstream servers are set, one is selected according to `upstream_selector` for each request. With `upstream_selector = "random"`, a random upstream server will be chosen for each request.
```toml
# available selector: random (default) or weighted_round_robin or lvs_weighted_round_robin
upstream_selector = "random"
```
### Example configuration: Apache ### Example configuration: Apache
```bash
SSLProtocol TLSv1.2
SSLHonorCipherOrder On
SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+3DES:!aNULL:!MD5:!DSS:!eNULL:!EXP:!LOW:!MD5
SSLUseStapling on
SSLStaplingCache shmcb:/var/lib/apache2/stapling_cache(512000)
SSLProtocol TLSv1.2 <VirtualHost *:443>
SSLHonorCipherOrder On ServerName MY_SERVER_NAME
SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+3DES:!aNULL:!MD5:!DSS:!eNULL:!EXP:!LOW:!MD5 Protocols h2 http/1.1
SSLUseStapling on ProxyPass /dns-query http://[::1]:8053/dns-query
SSLStaplingCache shmcb:/var/lib/apache2/stapling_cache(512000) ProxyPassReverse /dns-query http://[::1]:8053/dns-query
</VirtualHost>
<VirtualHost *:443> ```
ServerName MY_SERVER_NAME
Protocols h2 http/1.1
ProxyPass /dns-query http://[::1]:8053/dns-query
ProxyPassReverse /dns-query http://[::1]:8053/dns-query
</VirtualHost>
(Credit: [Joan Moreau](https://github.com/m13253/dns-over-https/issues/51#issuecomment-526820884)) (Credit: [Joan Moreau](https://github.com/m13253/dns-over-https/issues/51#issuecomment-526820884))
### Example configuration: Nginx ### Example configuration: Nginx
```bash
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name MY_SERVER_NAME;
server { server_tokens off;
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name MY_SERVER_NAME;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.3 requires nginx >= 1.13.0
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
ssl_early_data off; # 0-RTT, enable if desired - Requires nginx >= 1.15.4
resolver 1.1.1.1 valid=300s; # Replace with your local resolver
resolver_timeout 5s;
# HTTP Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=63072000";
ssl_certificate /path/to/your/server/certificates/fullchain.pem;
ssl_certificate_key /path/to/your/server/certificates/privkey.pem;
location /dns-query {
proxy_pass http://localhost:8053/dns-query;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.3 requires nginx >= 1.13.0
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
ssl_early_data off; # 0-RTT, enable if desired - Requires nginx >= 1.15.4
resolver 1.1.1.1 valid=300s; # Replace with your local resolver
resolver_timeout 5s;
# HTTP Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=63072000";
ssl_certificate /path/to/your/server/certificates/fullchain.pem;
ssl_certificate_key /path/to/your/server/certificates/privkey.pem;
location /dns-query {
proxy_pass http://localhost:8053/dns-query;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
(Credit: [Cipherli.st](https://cipherli.st/)) (Credit: [Cipherli.st](https://cipherli.st/))
### Example configuration: Caddy ### Example configuration: Caddy (v2)
```bash
my.server.name {
reverse_proxy * localhost:8053
tls your@email.address
try_files {path} {path}/index.php /index.php?{query}
}
```
### Example configuration: Docker Compose + Traefik + Unbound (Raspberry Pi/Linux/Mac) [linux/amd64,linux/arm64,linux/arm/v7]
https://MY_SERVER_NAME { ```yaml
log / syslog "{remote} - {user} [{when}] \"{method} {scheme}://{host}{uri} {proto}\" {status} {size} \"{>Referer}\" \"{>User-Agent}\" {>X-Forwarded-For}" version: '2.2'
errors syslog networks:
gzip default:
proxy /dns-query http://[::1]:18053 {
header_upstream Host {host} services:
header_upstream X-Real-IP {remote} proxy:
header_upstream X-Forwarded-For {>X-Forwarded-For},{remote} # The official v2 Traefik docker image
header_upstream X-Forwarded-Proto {scheme} image: traefik:v2.3
} hostname: proxy
root /var/www networks:
tls { - default
ciphers ECDHE-ECDSA-WITH-CHACHA20-POLY1305 ECDHE-RSA-WITH-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 environment:
curves X25519 p384 p521 TRAEFIK_ACCESSLOG: "true"
must_staple TRAEFIK_API: "true"
} TRAEFIK_PROVIDERS_DOCKER: "true"
} TRAEFIK_API_INSECURE: "true"
TRAEFIK_PROVIDERS_DOCKER_NETWORK: "${STACK}_default"
# DNS provider specific environment variables for DNS Challenge using route53 (AWS)
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
AWS_REGION: ${AWS_REGION}
AWS_HOSTED_ZONE_ID: ${AWS_HOSTED_ZONE_ID}
ports:
# The HTTP port
- "80:80"
# The HTTPS port
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
command:
#- "--log.level=DEBUG"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
# Providers list:
# https://docs.traefik.io/https/acme/#providers
# https://go-acme.github.io/lego/dns/
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=route53"
# Enable below line to use staging letsencrypt server.
#- "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
- ./data/proxy/certs:/certs
doh-server:
image: satishweb/doh-server:latest
hostname: doh-server
networks:
- default
environment:
# Enable below line to see more logs
# DEBUG: "1"
UPSTREAM_DNS_SERVER: "udp:unbound:53"
DOH_HTTP_PREFIX: "${DOH_HTTP_PREFIX}"
DOH_SERVER_LISTEN: ":${DOH_SERVER_LISTEN}"
DOH_SERVER_TIMEOUT: "10"
DOH_SERVER_TRIES: "3"
DOH_SERVER_VERBOSE: "false"
#volumes:
# - ./doh-server.conf:/server/doh-server.conf
# - ./app-config:/app-config
depends_on:
- unbound
labels:
- "traefik.enable=true"
- "traefik.http.routers.doh-server.rule=Host(`${SUBDOMAIN}.${DOMAIN}`) && Path(`${DOH_HTTP_PREFIX}`)"
- "traefik.http.services.doh-server.loadbalancer.server.port=${DOH_SERVER_LISTEN}"
- "traefik.http.middlewares.mw-doh-compression.compress=true"
- "traefik.http.routers.doh-server.tls=true"
- "traefik.http.middlewares.mw-doh-tls.headers.sslredirect=true"
- "traefik.http.middlewares.mw-doh-tls.headers.sslforcehost=true"
- "traefik.http.routers.doh-server.tls.certresolver=letsencrypt"
- "traefik.http.routers.doh-server.tls.domains[0].main=${DOMAIN}"
- "traefik.http.routers.doh-server.tls.domains[0].sans=${SUBDOMAIN}.${DOMAIN}"
# Protection from requests flood
- "traefik.http.middlewares.mw-doh-ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.mw-doh-ratelimit.ratelimit.burst=50"
- "traefik.http.middlewares.mw-doh-ratelimit.ratelimit.period=10s"
unbound:
image: satishweb/unbound:latest
hostname: unbound
networks:
- default
ports:
# Disable these ports if DOH server is the only client
- 53:53/tcp
- 53:53/udp
volumes:
- ./unbound.sample.conf:/templates/unbound.sample.conf
- ./data/unbound/custom:/etc/unbound/custom
# Keep your custom.hosts file inside custom folder
#environment:
# DEBUG: "1"
````
> Complete Guide available at: https://github.com/satishweb/docker-doh
> IPV6 Support for Docker Compose based configuration TBA
## 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. records. An instance of [Pi Hole](https://pi-hole.net) could also be used to validate DNS signatures as well as provide other capabilities.
## EDNS0-Client-Subnet (GeoDNS) ## EDNS0-Client-Subnet (GeoDNS)

View File

@@ -25,6 +25,7 @@ package main
import ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
@@ -39,7 +40,7 @@ import (
"github.com/m13253/dns-over-https/doh-client/config" "github.com/m13253/dns-over-https/doh-client/config"
"github.com/m13253/dns-over-https/doh-client/selector" "github.com/m13253/dns-over-https/doh-client/selector"
"github.com/m13253/dns-over-https/json-dns" 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"
@@ -247,6 +248,7 @@ func (c *Client) newHTTPClient() error {
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 {
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) {
@@ -305,7 +307,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 +358,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
@@ -471,7 +473,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

View File

@@ -47,15 +47,16 @@ type upstream struct {
} }
type others struct { 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"`
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",
@@ -132,3 +132,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

@@ -35,7 +35,7 @@ import (
"strings" "strings"
"github.com/m13253/dns-over-https/doh-client/selector" "github.com/m13253/dns-over-https/doh-client/selector"
"github.com/m13253/dns-over-https/json-dns" jsondns "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -44,7 +44,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{
@@ -67,6 +67,9 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
udpSize := uint16(512) udpSize := uint16(512)
if opt := r.IsEdns0(); opt != nil { if opt := r.IsEdns0(); opt != nil {
udpSize = opt.UDPSize() udpSize = opt.UDPSize()
if opt.Do() {
requestURL += "&do=1"
}
} }
ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r) ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r)
@@ -77,7 +80,7 @@ func (c *Client) generateRequestGoogle(ctx context.Context, w dns.ResponseWriter
req, err := http.NewRequest(http.MethodGet, requestURL, nil) req, err := http.NewRequest(http.MethodGet, requestURL, nil)
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{
@@ -108,7 +111,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{
@@ -118,7 +121,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,
@@ -145,7 +148,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)
@@ -157,8 +160,9 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
if respJSON.Status != dns.RcodeSuccess && respJSON.Comment != "" { if respJSON.Status != dns.RcodeSuccess && respJSON.Comment != "" {
log.Printf("DNS error: %s\n", respJSON.Comment) log.Printf("DNS error: %s\n", respJSON.Comment)
} }
fixEmptyNames(&respJSON)
fullReply := jsonDNS.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask) fullReply := jsondns.Unmarshal(req.reply, &respJSON, req.udpSize, req.ednsClientNetmask)
buf, err := fullReply.Pack() buf, err := fullReply.Pack()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@@ -177,3 +181,19 @@ func (c *Client) parseResponseGoogle(ctx context.Context, w dns.ResponseWriter,
} }
w.Write(buf) w.Write(buf)
} }
// Fix DNS response empty []RR.Name
// Additional section won't be rectified
// see: https://stackoverflow.com/questions/52136176/what-is-additional-section-in-dns-and-how-it-works
func fixEmptyNames(respJSON *jsondns.Response) {
for i := range respJSON.Answer {
if respJSON.Answer[i].Name == "" {
respJSON.Answer[i].Name = "."
}
}
for i := range respJSON.Authority {
if respJSON.Authority[i].Name == "" {
respJSON.Authority[i].Name = "."
}
}
}

View File

@@ -36,7 +36,7 @@ import (
"time" "time"
"github.com/m13253/dns-over-https/doh-client/selector" "github.com/m13253/dns-over-https/doh-client/selector"
jsonDNS "github.com/m13253/dns-over-https/json-dns" jsondns "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -90,7 +90,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{
@@ -107,7 +107,7 @@ func (c *Client) generateRequestIETF(ctx context.Context, w dns.ResponseWriter,
req, err = http.NewRequest(http.MethodGet, requestURL, nil) req, err = http.NewRequest(http.MethodGet, requestURL, nil)
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 +118,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 +149,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 +159,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,

View File

@@ -24,6 +24,6 @@
package main package main
const ( const (
VERSION = "2.2.1" VERSION = "2.2.5"
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"` Listen []string `toml:"listen"`
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"`
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"` 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"`
TLSClientAuthCA string `toml:"tls_client_auth_ca"`
} }
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,7 +34,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/m13253/dns-over-https/json-dns" jsondns "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"
) )
@@ -170,11 +170,11 @@ func (s *Server) parseRequestGoogle(ctx context.Context, w http.ResponseWriter,
} }
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

@@ -36,7 +36,7 @@ import (
"strings" "strings"
"time" "time"
jsonDNS "github.com/m13253/dns-over-https/json-dns" jsondns "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -125,6 +125,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 +134,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
ednsClientNetmask = 24 if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 32
} else {
ednsClientNetmask = 24
ednsClientAddress = ednsClientAddress.Mask(net.CIDRMask(24, 32))
}
} else { } else {
ednsClientFamily = 2 ednsClientFamily = 2
ednsClientNetmask = 56 if s.conf.ECSUsePreciseIP {
ednsClientNetmask = 128
} else {
ednsClientNetmask = 56
ednsClientAddress = ednsClientAddress.Mask(net.CIDRMask(56, 128))
}
} }
edns0Subnet = new(dns.EDNS0_SUBNET) edns0Subnet = new(dns.EDNS0_SUBNET)
edns0Subnet.Code = dns.EDNS0SUBNET edns0Subnet.Code = dns.EDNS0SUBNET
@@ -156,12 +167,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
} }

View File

@@ -25,7 +25,10 @@ package main
import ( import (
"context" "context"
"crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"math/rand" "math/rand"
"net" "net"
@@ -35,7 +38,7 @@ import (
"time" "time"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
jsonDNS "github.com/m13253/dns-over-https/json-dns" jsondns "github.com/m13253/dns-over-https/json-dns"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -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 := ioutil.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 != "" {
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 { } else {
err = http.ListenAndServe(addr, servemux) err = http.ListenAndServe(addr, servemux)
} }
@@ -136,6 +175,18 @@ func (s *Server) Start() error {
func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) { func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
if strings.ContainsRune(realIP, ':') {
r.RemoteAddr = "[" + realIP + "]:0"
} else {
r.RemoteAddr = realIP + ":0"
}
_, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
r.RemoteAddr = realIP
}
}
w.Header().Set("Access-Control-Allow-Headers", "Content-Type") w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS, POST") w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS, POST")
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
@@ -204,14 +255,14 @@ 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
} }
@@ -220,7 +271,7 @@ func (s *Server) handlerFunc(w http.ResponseWriter, r *http.Request) {
var err error var err error
req, 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
} }
@@ -234,12 +285,17 @@ 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")
if strings.ToLower(noEcs) == "true" {
return nil
}
XForwardedFor := r.Header.Get("X-Forwarded-For") XForwardedFor := r.Header.Get("X-Forwarded-For")
if XForwardedFor != "" { if XForwardedFor != "" {
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
} }
} }
@@ -248,15 +304,17 @@ 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
@@ -283,7 +341,6 @@ func (s *Server) indexQuestionType(msg *dns.Msg, qtype uint16) int {
} }
func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequest, err error) { func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequest, err error) {
// TODO(m13253): Make ctx work. Waiting for a patch for ExchangeContext from miekg/dns.
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)]
@@ -296,23 +353,23 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
return nil, &configError{"invalid DNS type"} return nil, &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.Exchange(req.request, upstream) req.response, _, err = s.tcpClientTLS.ExchangeContext(ctx, req.request, upstream)
case "tcp", "udp": case "tcp", "udp":
// Use TCP if always configured to or if the Query type dictates it (AXFR) // Use TCP if always configured to or if the Query type dictates it (AXFR)
if t == "tcp" || (s.indexQuestionType(req.request, dns.TypeAXFR) > -1) { if t == "tcp" || (s.indexQuestionType(req.request, dns.TypeAXFR) > -1) {
req.response, _, err = s.tcpClient.Exchange(req.request, upstream) req.response, _, err = s.tcpClient.ExchangeContext(ctx, req.request, upstream)
} else { } else {
req.response, _, err = s.udpClient.Exchange(req.request, upstream) req.response, _, err = s.udpClient.ExchangeContext(ctx, req.request, upstream)
if err == nil && req.response != nil && req.response.Truncated { if err == nil && req.response != nil && req.response.Truncated {
log.Println(err) log.Println(err)
req.response, _, err = s.tcpClient.Exchange(req.request, upstream) req.response, _, err = s.tcpClient.ExchangeContext(ctx, req.request, upstream)
} }
// Retry with TCP if this was an IXFR request and we only received an SOA // Retry with TCP if this was an IXFR request and we only received an SOA
if (s.indexQuestionType(req.request, dns.TypeIXFR) > -1) && 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) {
req.response, _, err = s.tcpClient.Exchange(req.request, upstream) req.response, _, err = s.tcpClient.ExchangeContext(ctx, req.request, upstream)
} }
} }
} }

View File

@@ -24,6 +24,6 @@
package main package main
const ( const (
VERSION = "2.2.1" VERSION = "2.2.5"
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)"
) )

9
go.mod
View File

@@ -5,8 +5,9 @@ go 1.12
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/gorilla/handlers v1.4.0 github.com/gorilla/handlers v1.4.0
github.com/miekg/dns v1.1.22 github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect github.com/miekg/dns v1.1.41
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect golang.org/x/net v0.0.0-20210324205630-d1beb07c2056
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 // indirect
) )

52
go.sum
View File

@@ -2,35 +2,47 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA= github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9 h1:w66aaP3c6SIQ0pi3QH1Tb4AMO3aWoEPxd1CNvLphbkA=
github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/infobloxopen/go-trees v0.0.0-20200715205103-96a057b8dfb9/go.mod h1:BaIJzjD2ZnHmx2acPF6XfGLPzNCMiBbMRqJr+8/8uRI=
github.com/miekg/dns v1.1.22 h1:Jm64b3bO9kP43ddLjL2EY3Io6bmy1qGb9Xxz6TqS6rc= github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/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/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
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-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-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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a h1:Yu34BogBivvmu7SAzHHaB9nZWH5D1C+z3F1jyIaYZSQ= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056 h1:sANdAef76Ioam9aQUUdcAqricwY/WUaMc4+7LY4eGg8=
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-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-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-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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -21,7 +21,7 @@
DEALINGS IN THE SOFTWARE. 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,111 @@
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
net.IPNet{
net.IP{0, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Private-Use Networks
net.IPNet{
net.IP{10, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Shared Address Space
net.IPNet{
net.IP{100, 64, 0, 0},
net.IPMask{255, 192, 0, 0},
},
// Loopback
net.IPNet{
net.IP{127, 0, 0, 0},
net.IPMask{255, 0, 0, 0},
},
// Link Local
net.IPNet{
net.IP{169, 254, 0, 0},
net.IPMask{255, 255, 0, 0},
},
// Private-Use Networks
net.IPNet{
net.IP{172, 16, 0, 0},
net.IPMask{255, 240, 0, 0},
},
// DS-Lite
net.IPNet{
net.IP{192, 0, 0, 0},
net.IPMask{255, 255, 255, 248},
},
// 6to4 Relay Anycast
net.IPNet{
net.IP{192, 88, 99, 0},
net.IPMask{255, 255, 255, 0},
},
// Private-Use Networks
net.IPNet{
net.IP{192, 168, 0, 0},
net.IPMask{255, 255, 0, 0},
},
// Reserved for Future Use & Limited Broadcast
net.IPNet{
net.IP{240, 0, 0, 0},
net.IPMask{240, 0, 0, 0},
},
}
// RFC6890 func init() {
var localIPv6Nets = []net.IPNet{ defaultFilter = iptree.NewTree()
// RFC6890
// This host on this network
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{0, 0, 0, 0},
Mask: net.IPMask{255, 0, 0, 0},
}, struct{}{})
// Private-Use Networks
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{10, 0, 0, 0},
Mask: net.IPMask{255, 0, 0, 0},
}, struct{}{})
// Shared Address Space
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{100, 64, 0, 0},
Mask: net.IPMask{255, 192, 0, 0},
}, struct{}{})
// Loopback
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{127, 0, 0, 0},
Mask: net.IPMask{255, 0, 0, 0},
}, struct{}{})
// Link Local
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{169, 254, 0, 0},
Mask: net.IPMask{255, 255, 0, 0},
}, struct{}{})
// Private-Use Networks
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{172, 16, 0, 0},
Mask: net.IPMask{255, 240, 0, 0},
}, struct{}{})
// DS-Lite
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{192, 0, 0, 0},
Mask: net.IPMask{255, 255, 255, 248},
}, struct{}{})
// 6to4 Relay Anycast
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{192, 88, 99, 0},
Mask: net.IPMask{255, 255, 255, 0},
}, struct{}{})
// Private-Use Networks
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{192, 168, 0, 0},
Mask: net.IPMask{255, 255, 0, 0},
}, struct{}{})
// Reserved for Future Use & Limited Broadcast
defaultFilter.InplaceInsertNet(&net.IPNet{
IP: net.IP{240, 0, 0, 0},
Mask: net.IPMask{240, 0, 0, 0},
}, struct{}{})
// RFC6890
// Unspecified & Loopback Address // 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,12 +21,32 @@
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
package jsonDNS package jsondns
import ( import (
"encoding/json"
"time" "time"
) )
type QuestionList []Question
func (ql *QuestionList) UnmarshalJSON(b []byte) error {
// Fix variant question response in Response.Question
//
// Solution taken from:
// https://engineering.bitnami.com/articles/dealing-with-json-with-non-homogeneous-types-in-go.html
// https://archive.is/NU4zR
if len(b) > 0 && b[0] == '[' {
return json.Unmarshal(b, (*[]Question)(ql))
}
var q Question
if err := json.Unmarshal(b, &q); err != nil {
return err
}
*ql = []Question{q}
return nil
}
type Response struct { type Response struct {
// Standard DNS response code (32 bit integer) // Standard DNS response code (32 bit integer)
Status uint32 `json:"Status"` Status uint32 `json:"Status"`
@@ -40,13 +60,13 @@ type Response struct {
// FIXME: We don't have DNSSEC yet! This bit is not reliable! // FIXME: We don't have DNSSEC yet! This bit is not reliable!
AD bool `json:"AD"` AD bool `json:"AD"`
// Whether the client asked to disable DNSSEC // Whether the client asked to disable DNSSEC
CD bool `json:"CD"` CD bool `json:"CD"`
Question []Question `json:"Question"` Question QuestionList `json:"Question"`
Answer []RR `json:"Answer,omitempty"` Answer []RR `json:"Answer,omitempty"`
Authority []RR `json:"Authority,omitempty"` Authority []RR `json:"Authority,omitempty"`
Additional []RR `json:"Additional,omitempty"` Additional []RR `json:"Additional,omitempty"`
Comment string `json:"Comment,omitempty"` Comment string `json:"Comment,omitempty"`
EdnsClientSubnet string `json:"edns_client_subnet,omitempty"` EdnsClientSubnet string `json:"edns_client_subnet,omitempty"`
// Least time-to-live // Least time-to-live
HaveTTL bool `json:"-"` HaveTTL bool `json:"-"`
LeastTTL uint32 `json:"-"` LeastTTL uint32 `json:"-"`

View File

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