Repository: TuxInvader/nginx-dns
Branch: master
Commit: 29ee0d7e45e4
Files: 19
Total size: 75.2 KB
Directory structure:
gitextract_h3c4240l/
├── LICENSE
├── README.md
├── docs/
│ └── nginx-dns-over-https.md
├── examples/
│ ├── nginx-dns-routing.conf
│ ├── nginx-dns-simple.conf
│ ├── nginx-doh-and-dot-to-dns.conf
│ ├── nginx-doh-and-dot-to-dot.conf
│ ├── nginx-dot-to-dns-simple.conf
│ ├── nginx-dot-to-dot-routing.conf
│ ├── nginx-plus-filtering.conf
│ └── test.conf
├── nginx-doh.conf
├── nginx-glb.conf
├── njs.d/
│ └── dns/
│ ├── dns.js
│ ├── glb.js
│ ├── libdns.js
│ └── test.js
└── ssl/
├── certs/
│ └── doh.local.pem
└── private/
└── doh.local.pem
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
BSD 2-Clause License
Copyright (c) 2022, Mark Boddington
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# NGINX DNS (DNS/DoT/DoH)
> The `v2` branch is migrating to `Buffers` due to NJS deprecating the `String` byte-array functions.
> Please test and raise issues if you find them. Thank you!
This repository contains some NJS code, and example configuration files for using NGINX with DNS services.
NGINX can be used to perform load balancing for DNS (TCP/UDP), and also DNS over TLS (DoT) and DNS over HTTPS (DoH)
NGINX can also be used to provide Global Server Load Balancing (GSLB).
See the example configuration files in the [examples](examples) folder.
## Setup
Copy the njs.d folder into /etc/nginx/ and one of the NGINX DoH [examples](examples) to /etc/nginx/nginx.conf
The ssl folder contains a test certificate, you will likely want to generate and use your own certificate and update the nginx.conf file accordingly.
## Simple DNS
NGINX can do simple DNS load balancing, without the need for NJS, using the standard Stream module directives.
```
stream {
# DNS upstream pool.
upstream dns {
zone dns 64k;
server 8.8.8.8:53;
}
# DNS Server. Listens on both TCP and UDP
server {
listen 53;
listen 53 udp;
proxy_responses 1;
proxy_pass dns;
}
}
```
However if you want to carry out layer 7 inspection of the DNS traffic for logging or routing purposes, then you will need to use the NJS module
included in this repository.
To perform DNS routing, you need to make a `js_preread` function call in the server context, and use a `js_set` function with a `map`.
For example:
```
stream {
js_import /etc/nginx/njs.d/dns/dns.js;
js_set $dns_qname dns.get_qname;
map $dns_qname $upstream_pool {
hostnames;
*.nginx one;
default two;
}
upstream one {
...
}
upstream two {
...
}
server {
listen 53 udp;
js_preread dns.preread_dns_request;
proxy_responses 1;
proxy_pass $upstream_pool;
}
}
```
## DNS over TLS (DoT) and DNS over HTTPS (DoH) Gateway
NGINX can act as a DNS(TCP) <-> DNS over TLS (DoT) gateway without any NJS functions. Eg:
```
upstream dns {
zone dns 64k;
server 8.8.8.8:53;
}
upstream dot {
zone dot 64k;
server 8.8.8.8:853;
}
server {
listen 53;
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
proxy_ssl on;
proxy_pass dot;
}
```
The above example will accpet DNS and DoT requests, and forward them to a DoT upstream. If your upstream is DNS, and you want to terminate DoT on NGINX, then remove the `proxy_ssl on;` directive, and change the `proxy_pass` directive to use the standard DNS upstream.
NJS is required if you want to act as a gateway between DoH and DNS/DoT.
See the example configuration files in the [examples](examples) folder.
The full configuration has a HTTP/2 service listening for requests, and does a proxy_pass for requests to /dns-query.
We proxy to an internal stream service on port 8053, which uses js_filter to pull out the DNS packet from the HTTP wrapper,
and forward onto an upstream DNS(TCP) or DoT server.
The result is then wrapped back up in a HTTP response and passed to the HTTP/2 service for delivery to the client.
NGINX can log as much or as little as you like, and the NJS allows you to process information in the DNS requests and
responses.
See: [docs/nginx-dns-over-https](docs/nginx-dns-over-https.md) for more information
## NGINX GSLB (work-in-progress)
Use the nginx-glb.conf file to run an GSLB service.
Copy the njs.d folder into /etc/nginx/ and the nginx-glb.conf to /etc/nginx/nginx.conf
TODO - Describe the example configuration and how to customise it.
================================================
FILE: docs/nginx-dns-over-https.md
================================================
## DNS over HTTPS (DoH) Gateway
Use the nginx-doh.conf file to run a DoH gateway.
Copy the njs.d and ssl folders into /etc/nginx/ and the nginx-doh.conf to /etc/nginx/nginx.conf
### Simple DNS(TCP) and DNS over TLS (DoT)
NGINX can act as a DNS(TCP) <-> DNS over TLS (DoT) gateway without any NJS functions. Eg:
```
upstream dns {
zone dns 64k;
server 8.8.8.8:53;
}
upstream dot {
zone dot 64k;
server 8.8.8.8:853;
}
server {
listen 53;
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
proxy_ssl on;
proxy_pass dot;
}
```
### DNS over HTTPS (DoH)
NJS is required if you want to act as a gateway between DoH and DNS/DoT. In this case we need some NJS code, and the
full configuration in nginx-glb.conf.
The full configuration has a HTTP/2 service listening for requests, and does a proxy_pass for requests to /dns-query.
We proxy to an internal stream service on port 8053, which uses js_filter to pull out the DNS packet from the HTTP wrapper,
and forward onto an upstream DNS(TCP) or DoT server.
The result is then wrapped back up in a HTTP response and passed to the HTTP/2 service for delivery to the client.
#### NGINX Stream Server for back-end
Lets look at the stream service first:
```
server {
listen 127.0.0.1:8053;
js_filter dns.filter_doh_request;
proxy_ssl on;
proxy_pass dot;
}
```
We listen on the loopback interface on port 8053. HTTP/1.0 requests will be passed to us from the NGINX http service. The `js_filter`
will find the DNS packet encoded in the request, and forward it on to the upstream DoT service.
#### NGINX HTTP/2 service for the front-end
Traffic arrives at the stream service via a HTTP/2 server:
```
upstream dohloop {
zone dohloop 64k;
server 127.0.0.1:8053;
}
proxy_cache_path /var/cache/nginx/doh_cache levels=1:2 keys_zone=doh_cache:10m;
server {
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
proxy_cache_methods GET POST;
location / {
return 404 "404 Not Found\n";
}
location /dns-query {
proxy_http_version 1.0;
proxy_cache doh_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body;
proxy_pass http://dohloop;
}
}
```
We listen on the standard HTTPS port for incoming http requests. We return a 404 response to all requests except for those which match our
`/dns-query` location. All dns-queries are forwarded onto the stream service as HTTP/1.0 requests.
#### NGINX Processing Options
The NJS code can perform varying degress of processing on the DNS packets. The fastest for DNS is to do no processing (level 0), but enabling some
processing (level 2) allows NGINX to gather the necessary intelligence (Resource Record TTLs) to enable a HTTP Content-Cache for the DoH requests.
At levels less than 2, we will cache responses, but for just 10 seconds.
Change this setting in the `njs.d/dns/doh.js` file
```
/**
* DNS Decode Level
* 0: No decoding, minimal processing required to strip packet from HTTP wrapper (fastest)
* 1: Parse DNS Header and Question. We can log the Question, Class, Type, and Result Code
* 2: As 1, but also parse answers. We can log the answers, and also cache responses according to TTL.
* 3: Very Verbose, log everything as above, but also write packet data to error log (slowest)
**/
var $dns_decode_level = 0;
```
================================================
FILE: examples/nginx-dns-routing.conf
================================================
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
stream {
# logging
log_format dns '$remote_addr [$time_local] $protocol "$dns_qname" "$upstream_pool"';
access_log /var/log/nginx/dns-access.log dns;
# import NJS module
js_import /etc/nginx/njs.d/dns/dns.js;
# NJS function to get the dns_qname, requires a js_preread in the server to populate the variable from the DNS packet
js_set $dns_qname dns.get_qname;
# This maps the qname domain to the DNS server for routing
map $dns_qname $upstream_pool {
hostnames;
*.nginx dnsmasq;
*.k8s dnsmasq;
default google;
}
# Google upstream
upstream google {
zone dns 64k;
server 8.8.8.8:53;
}
# dnsmasq local upstream
upstream dnsmasq {
zone dns 64k;
server 192.168.64.1:53;
}
# DNS(TCP) Serverr
server {
listen 53;
js_preread dns.preread_dns_request;
proxy_pass $upstream_pool;
}
# DNS(UDP) Server
server {
listen 53 udp;
js_preread dns.preread_dns_request;
proxy_responses 1;
proxy_pass $upstream_pool;
}
}
================================================
FILE: examples/nginx-dns-simple.conf
================================================
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
stream {
# DNS upstream pool.
upstream dns {
zone dns 64k;
server 8.8.8.8:53;
}
# DNS Server. Listens on both TCP and UDP
server {
listen 53;
listen 53 udp;
proxy_responses 1;
proxy_pass dns;
}
}
================================================
FILE: examples/nginx-doh-and-dot-to-dns.conf
================================================
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# logging directives
log_format doh '$remote_addr - $remote_user [$time_local] "$request" '
'[ $msec, $request_time, $upstream_response_time $pipe ] '
'$status $body_bytes_sent "$http_x_forwarded_for" '
'$upstream_http_x_dns_question $upstream_http_x_dns_type '
'$upstream_http_x_dns_result '
'$upstream_http_x_dns_ttl $upstream_http_x_dns_answers '
'$upstream_cache_status';
access_log /var/log/nginx/doh-access.log doh;
# This upstream connects to a local Stream service which converts HTTP -> DNS
upstream dohloop {
zone dohloop 64k;
server 127.0.0.1:8053;
keepalive_timeout 60s;
keepalive_requests 100;
keepalive 10;
}
# Proxy Cache storage - so we can cache the DoH response from the upstream
proxy_cache_path /var/cache/nginx/doh_cache levels=1:2 keys_zone=doh_cache:10m;
# The DoH server block
server {
# Listen on standard HTTPS port, and accept HTTP2, with SSL termination
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
ssl_session_cache shared:ssl_cache:10m;
ssl_session_timeout 10m;
# DoH may use GET or POST requests, Cache both
proxy_cache_methods GET POST;
# Return 404 to all responses, except for those using our published DoH URI
location / {
return 404 "404 Not Found\n";
}
# This is our published DoH URI
location /dns-query {
# Proxy HTTP/1.1, clear the connection header to enable Keep-Alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Enable Cache, and set the cache_key to include the request_body
proxy_cache doh_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body;
# proxy pass to the dohloop upstream
proxy_pass http://dohloop;
}
}
}
# DNS Stream Services
stream {
# DNS logging
log_format dns '$remote_addr [$time_local] $protocol "$dns_qname"';
access_log /var/log/nginx/dns-access.log dns;
# Import the NJS module
js_import /etc/nginx/njs.d/dns/dns.js;
# The $dns_qname variable can be populated by preread calls, and can be used for DNS routing
js_set $dns_qname dns.get_qname;
# DNS upstream pool.
upstream dns {
zone dns 64k;
server 8.8.8.8:53;
}
# DNS(TCP) and DNS over TLS (DoT) Server
# Terminate DoT and DNS TCP, and proxy onto standard DNS
server {
listen 53;
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
js_preread dns.preread_dns_request;
proxy_pass dns;
}
# DNS(UDP) Server
# DNS UDP proxy onto DNS UDP
server {
listen 53 udp;
proxy_responses 1;
js_preread dns.preread_dns_request;
proxy_pass dns;
}
# DNS over HTTPS (gateway) Service
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
listen 127.0.0.1:8053;
js_filter dns.filter_doh_request;
proxy_pass dns;
}
}
================================================
FILE: examples/nginx-doh-and-dot-to-dot.conf
================================================
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# logging directives
log_format doh '$remote_addr - $remote_user [$time_local] "$request" '
'[ $msec, $request_time, $upstream_response_time $pipe ] '
'$status $body_bytes_sent "$http_x_forwarded_for" '
'$upstream_http_x_dns_question $upstream_http_x_dns_type '
'$upstream_http_x_dns_result '
'$upstream_http_x_dns_ttl $upstream_http_x_dns_answers '
'$upstream_cache_status';
access_log /var/log/nginx/doh-access.log doh;
# This upstream connects to a local Stream service which converts HTTP -> DNS
upstream dohloop {
zone dohloop 64k;
server 127.0.0.1:8053;
keepalive_timeout 60s;
keepalive_requests 100;
keepalive 10;
}
# Proxy Cache storage - so we can cache the DoH response from the upstream
proxy_cache_path /var/cache/nginx/doh_cache levels=1:2 keys_zone=doh_cache:10m;
# The DoH server block
server {
# Listen on standard HTTPS port, and accept HTTP2, with SSL termination
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
ssl_session_cache shared:ssl_cache:10m;
ssl_session_timeout 10m;
# DoH may use GET or POST requests, Cache both
proxy_cache_methods GET POST;
# Return 404 to all responses, except for those using our published DoH URI
location / {
return 404 "404 Not Found\n";
}
# This is our published DoH URI
location /dns-query {
# Proxy HTTP/1.1, clear the connection header to enable Keep-Alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Enable Cache, and set the cache_key to include the request_body
proxy_cache doh_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body;
# proxy pass to the dohloop upstream
proxy_pass http://dohloop;
}
}
}
# DNS Stream Services
stream {
# DNS logging
log_format dns '$remote_addr [$time_local] $protocol "$dns_qname"';
access_log /var/log/nginx/dns-access.log dns;
# Import the NJS module
js_import /etc/nginx/njs.d/dns/dns.js;
# The $dns_qname variable can be populated by preread calls, and can be used for DNS routing
js_set $dns_qname dns.get_qname;
# DNS over TLS upstream pool
upstream dot {
zone dot 64k;
server 8.8.8.8:853;
}
# DNS(TCP) and DNS over TLS (DoT) Server
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
# DNS TCP
listen 53;
# DNS DoT
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
# This is used to pull out question for logging
js_preread dns.preread_dns_request;
# Enable SSL re-encryption for DoT connection upstream
proxy_ssl on;
proxy_pass dot;
}
# DNS over HTTPS (gateway) Service
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
listen 127.0.0.1:8053;
js_filter dns.filter_doh_request;
proxy_ssl on;
proxy_pass dot;
}
}
================================================
FILE: examples/nginx-dot-to-dns-simple.conf
================================================
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
# DNS Stream Services
stream {
# DNS upstream pool.
upstream dns {
zone dns 64k;
server 8.8.8.8:53;
}
# DNS(TCP) and DNS over TLS (DoT) Server
# Terminates DNS and DoT, then proxies on to standard DNS.
server {
listen 53;
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
proxy_pass dns;
}
# DNS(UDP) Server
server {
listen 53 udp;
proxy_responses 1;
proxy_pass dns;
}
}
================================================
FILE: examples/nginx-dot-to-dot-routing.conf
================================================
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
# DNS Stream Services
stream {
# DNS logging
log_format dns '$remote_addr [$time_local] $protocol "$dns_qname" "$upstream_pool"';
access_log /var/log/nginx/dns-access.log dns;
# Import the NJS module
js_import /etc/nginx/njs.d/dns/dns.js;
# The $dns_qname variable will be populated by preread calls, and used for DNS routing
js_set $dns_qname dns.get_qname;
# When doing DNS routing, use $dns_qname to map the questions to the upstream pools.
map $dns_qname $upstream_pool {
hostnames;
*.nginx dnsmasq;
*.k8s dnsmasq;
default google;
}
# upstream pools (google DoT)
upstream google {
zone dns 64k;
server 8.8.8.8:853;
}
# upstream pools (another DoT)
upstream dnsmasq {
zone dns 64k;
server 192.168.64.1:853;
}
# DNS(TCP) and DNS over TLS (DoT) Server
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
listen 53;
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
js_preread dns.preread_dns_request;
proxy_ssl on;
proxy_pass $upstream_pool;
}
}
================================================
FILE: examples/nginx-plus-filtering.conf
================================================
#
# This config shows an example of filtering DNS requests using the Key/value store available in NGINX Plus
# Push FQDNs into the dns_config key/value zone with a value of "blocked" or "blackhole" to have them scrubbed from DNS
# Alternatively push a CSV list of domains as the value to either "blocked_domains" or "blackhole_domains" to have
# any requests for records within those zones scrubbed.
#
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
# DNS Stream Services
stream {
# KeyValue store for blocking domains (NGINX Plus only)
keyval_zone zone=dns_config:64k state=/etc/nginx/zones/dns_config.zone;
keyval "blocked_domains" $blocked_domains zone=dns_config;
keyval "blackhole_domains" $blackhole_domains zone=dns_config;
keyval $dns_qname $scrub_action zone=dns_config;
# DNS logging
log_format dns '$remote_addr [$time_local] $protocol "$dns_qname" "$upstream_pool"';
access_log /var/log/nginx/dns-access.log dns;
# Import the NJS module
js_import /etc/nginx/njs.d/dns/dns.js;
# The $dns_qname variable can be populated by preread calls, and can be used for DNS routing
js_set $dns_qname dns.get_qname;
# The DNS response packet, if we're blocking the domain, this will be set.
js_set $dns_response dns.get_response;
# When doing DNS routing, use $dns_qname to map the questions to the upstream pools.
map $dns_qname $upstream {
hostnames;
*.nginx dnsmasq;
*.k8s dnsmasq;
default google;
}
# Set upstream to be the pool defined above if dns_response is empty, else pass to the block/blackhole upstream
map $dns_response $upstream_pool {
"blocked" blocked;
"blackhole" blackhole;
default $upstream;
}
# upstream pool for blocked requests (returns nxdomain)
upstream blocked {
zone blocked 64k;
server 127.0.0.1:9953;
}
# upstream pool for blacholed requests (returns 0.0.0.0)
upstream blackhole {
zone blackhole 64k;
server 127.0.0.1:9853;
}
# upstream pools (google DNS)
upstream google {
zone dns 64k;
server 8.8.8.8:53;
}
# upstream pools (another DNS)
upstream dnsmasq {
zone dns 64k;
server 192.168.64.1:5353;
}
# DNS(TCP) and DNS over TLS (DoT) Server
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
listen 53;
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
js_preread dns.preread_dns_request;
proxy_pass $upstream_pool;
}
# DNS(UDP) Server
# Upstream can only be another DNS(UDP) server.
server {
listen 53 udp;
js_preread dns.preread_dns_request;
proxy_responses 1;
proxy_pass $upstream_pool;
}
# Server for responding to blocked/blackholed responses
server {
listen 127.0.0.1:9953;
listen 127.0.0.1:9853;
listen 127.0.0.1:9953 udp;
listen 127.0.0.1:9853 udp;
js_preread dns.preread_dns_request;
return $dns_response;
}
}
================================================
FILE: examples/test.conf
================================================
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http { }
# DNS Stream Services
stream {
# DNS logging
log_format dns '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received "$dns_qname" "$upstream_addr"';
access_log /var/log/nginx/dns-access.log dns;
# Import the NJS module
js_import /etc/nginx/njs.d/dns/test.js;
# The $dns_qname variable can be populated by preread calls, and can be used for DNS routing
js_set $dns_qname test.get_qname;
# test server - responds to dns queries
server {
listen 5553;
listen 5553 udp;
js_var $test_result;
js_preread test.test_dns_encoder;
return $test_result;
}
# load balance to test server and parse responses
server {
listen 5554;
listen 5554 udp;
proxy_responses 1;
js_filter test.test_dns_decoder;
proxy_pass 127.0.0.1:5553;
}
}
================================================
FILE: nginx-doh.conf
================================================
#
# This config has bits of DNS/DoT/DoH all over it. See the examples folder for more targeted examples.
#
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# logging directives
log_format doh '$remote_addr - $remote_user [$time_local] "$request" '
'[ $msec, $request_time, $upstream_response_time $pipe ] '
'$status $body_bytes_sent "$http_x_forwarded_for" '
'$upstream_http_x_dns_question $upstream_http_x_dns_type '
'$upstream_http_x_dns_result '
'$upstream_http_x_dns_ttl $upstream_http_x_dns_answers '
'$upstream_cache_status';
access_log /var/log/nginx/doh-access.log doh;
# This upstream connects to a local Stream service which converts HTTP -> DNS
upstream dohloop {
zone dohloop 64k;
server 127.0.0.1:8053;
keepalive_timeout 60s;
keepalive_requests 100;
keepalive 10;
}
# Proxy Cache storage - so we can cache the DoH response from the upstream
proxy_cache_path /var/cache/nginx/doh_cache levels=1:2 keys_zone=doh_cache:10m;
# The DoH server block
server {
# Listen on standard HTTPS port, and accept HTTP2, with SSL termination
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
ssl_session_cache shared:ssl_cache:10m;
ssl_session_timeout 10m;
# DoH may use GET or POST requests, Cache both
proxy_cache_methods GET POST;
# Return 404 to all responses, except for those using our published DoH URI
location / {
return 404 "404 Not Found\n";
}
# This is our published DoH URI
location /dns-query {
# Proxy HTTP/1.1, clear the connection header to enable Keep-Alive
proxy_http_version 1.1;
proxy_set_header Connection "";
# Enable Cache, and set the cache_key to include the request_body
proxy_cache doh_cache;
proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body;
# proxy pass to the dohloop upstream
proxy_pass http://dohloop;
}
}
# enable API
server {
listen 8080;
location /api {
api write=on;
allow 127.0.0.1;
allow 192.168.64.1;
deny all;
}
}
}
# DNS Stream Services
stream {
# KeyValue store for blocking domains (NGINX Plus only)
keyval_zone zone=dns_config:64k state=/etc/nginx/zones/dns_config.zone;
keyval "blocked_domains" $blocked_domains zone=dns_config;
keyval "blackhole_domains" $blackhole_domains zone=dns_config;
keyval $dns_qname $scrub_action zone=dns_config;
# DNS logging
log_format dns '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received "$dns_qname" "$upstream_addr"';
access_log /var/log/nginx/dns-access.log dns;
# Import the NJS DNS module
js_import /etc/nginx/njs.d/dns/dns.js;
# The $dns_qname variable can be populated by preread calls, and can be used for DNS routing
js_set $dns_qname dns.get_qname;
# The DNS response packet, if we're blocking the domain, this will be set.
js_set $dns_response dns.get_response;
# When doing DNS routing, use $dns_qname to map the questions to the upstream pools.
map $dns_qname $upstream {
hostnames;
*.nginx dnsmasq;
*.k8s dnsmasq;
default google;
}
# Set upstream to be the pool defined above if dns_response is empty, else pass to the @block location
map $dns_response $upstream_pool {
"blocked" blocked;
"blackhole" blackhole;
default $upstream;
}
# upstream pool for blocked requests
upstream blocked {
zone blocked 64k;
server 127.0.0.1:9953;
}
upstream blackhole {
zone blackhole 64k;
server 127.0.0.1:9853;
}
# upstream pools (google DNS)
upstream google {
zone dns 64k;
server 8.8.8.8:53;
}
# upstream pools (another DNS)
upstream dnsmasq {
zone dns 64k;
server 192.168.64.1:5353;
}
# DNS upstream pool.
upstream dns {
zone dns 64k;
server 8.8.8.8:53;
}
# DNS over TLS upstream pool
upstream dot {
zone dot 64k;
server 8.8.8.8:853;
}
# DNS(TCP) and DNS over TLS (DoT) Server
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
listen 553;
listen 853 ssl;
ssl_certificate /etc/nginx/ssl/certs/doh.local.pem;
ssl_certificate_key /etc/nginx/ssl/private/doh.local.pem;
js_preread dns.preread_dns_request;
#proxy_ssl on;
proxy_pass $upstream_pool;
}
# DNS(UDP) Server
# Upstream can only be another DNS(UDP) server.
server {
listen 553 udp;
js_preread dns.preread_dns_request;
proxy_responses 1;
proxy_pass $upstream_pool;
}
# DNS over HTTPS (gateway) Service
# Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off.
server {
listen 127.0.0.1:8053;
js_filter dns.filter_doh_request;
proxy_ssl on;
proxy_pass dot;
}
# Server for sending blackhole/blocked responses
server {
listen 127.0.0.1:9953;
listen 127.0.0.1:9853;
listen 127.0.0.1:9953 udp;
listen 127.0.0.1:9853 udp;
js_preread dns.preread_dns_request;
return $dns_response;
}
}
================================================
FILE: nginx-glb.conf
================================================
user nginx;
worker_processes auto;
load_module modules/ngx_stream_js_module.so;
load_module modules/ngx_stream_geoip2_module.so;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
upstream www_netflix_com {
zone upstream_www_netflix_com 64k;
server 52.49.96.37; # eu-west-1
server 52.51.179.14 backup;
server 52.32.190.151; # us-west-2
server 52.41.193.16 backup;
}
match server_ok {
status 200-399;
}
server {
listen 127.0.0.1:8888;
server_name www.netflix.com;
set $test "foobar";
location / {
proxy_pass http://www_netflix_com;
proxy_set_header Host $host;
proxy_http_version 1.1;
health_check interval=10 fails=3 passes=3 match=server_ok;
}
}
server {
listen 127.0.0.1:80;
location /api/ {
api write=on;
allow 127.0.0.1;
deny all;
}
}
}
stream {
js_include /etc/nginx/njs.d/nginx_stream.js;
js_set $glb_response glb_get_response;
js_set $edns_subnet glb_get_edns_subnet;
keyval_zone zone=glb_config:64k state=/etc/nginx/zones/glb_config.zone;
keyval "www_netflix_com" $www_netflix_com zone=glb_config;
keyval "GLB_USE_EDNS" $glb_use_edns zone=glb_config;
# The following keys are needed if s.api() is unavailable
keyval "www_netflix_com_nodes" $www_netflix_com_nodes zone=glb_config;
keyval "www_netflix_com_geoip_52_49_96_37" $www_netflix_com_geoip_52_49_96_37 zone=glb_config;
keyval "www_netflix_com_geoip_52_51_179_14" $www_netflix_com_geoip_52_51_179_14 zone=glb_config;
keyval "www_netflix_com_geoip_52_32_190_151" $www_netflix_com_geoip_52_32_190_151 zone=glb_config;
keyval "www_netflix_com_geoip_52_41_193_16" $www_netflix_com_geoip_52_41_193_16 zone=glb_config;
# set $geoip_source to EDNS if we have one, or the $remote_addr if not
map $edns_subnet $geoip_source {
"" $remote_addr;
default $edns_subnet;
}
# get the country, latitude, and longitude from the GeoLite2 City DB
geoip2 /etc/geoip/GeoLite2-City.mmdb {
$geoip2_country_code default=GB source=$geoip_source country iso_code;
$geoip2_latitude default=51.52830 source=$geoip_source location latitude;
$geoip2_longitude default=0.0000 source=$geoip_source location longitude;
}
# process the DNS request
server {
listen 192.168.64.20:53 udp reuseport;
js_preread glb_process_request;
return $glb_response;
}
}
================================================
FILE: njs.d/dns/dns.js
================================================
import dns from "libdns.js";
export default {get_qname, get_response, preread_doh_request, preread_dns_request, filter_doh_request};
/**
* DNS Decode Level
* 0: No decoding, minimal processing required to strip packet from HTTP wrapper (fastest)
* 1: Parse DNS Header and Question. We can log the Question, Class, Type, and Result Code
* 2: As 1, but also parse answers. We can log the answers, and also cache responses in HTTP Content-Cache
* 3: Very Verbose, log everything as above, but also write packet data to error log (slowest)
**/
var dns_decode_level = 3;
/**
* DNS Debug Level
* Specify the decoding level at which we should log packet data to the error log.
* Default is level 3 (max decoding)
**/
var dns_debug_level = 3;
/**
* DNS Question Load Balancing
* Set this to true, if you want to pick the upstream pool based on the DNS Question.
* Doing so will disable HTTP KeepAlives for DoH so that we can create a new socket for each query
**/
var dns_question_balancing = false;
// The DNS Question name
var dns_name = Buffer.alloc(0);
function get_qname(s) {
return dns_name;
}
// The Optional DNS response, this is set when we want to block a specific domain
var dns_response = Buffer.alloc(0);
function get_response(s) {
return dns_response.toString();
}
// Encode the given number to two bytes (16 bit)
function to_bytes( number ) {
return Buffer.from( [ ((number>>8) & 0xff), (number & 0xff) ] );
}
function debug(s, msg) {
if ( dns_decode_level >= dns_debug_level ) {
s.warn(msg);
}
}
function process_doh_request(s, decode, scrub) {
s.on("upstream", function(data,flags) {
if ( data.length == 0 ) {
return;
}
var dataString = data.toString('utf8');
const lines = dataString.split("\r\n");
var bytes;
var packet;
if(lines[0].startsWith("GET")) {
var line = lines[0];
var path = line.split(" ")[1]
var params = path.split("?")[1]
var qs = params.split("&");
qs.some( param => {
if (param.startsWith("dns=") ) {
bytes = Buffer.from(param.slice(4), "base64url");
return true;
}
return false;
});
}
if(lines[0].startsWith("POST")) {
const index = lines.findIndex(line=>{
if(line.length == 0) {
return true;
}
})
if(index>0 && lines.length >= index + 1){
bytes = Buffer.from(lines[index + 1]);
}
}
if (bytes) {
debug(s, "process_doh_request: DNS Req: " + bytes.toString('hex') );
if (decode) {
packet = dns.parse_packet(bytes);
debug(s, "process_doh_request: DNS Req ID: " + packet.id );
dns.parse_question(packet);
debug(s,"process_doh_request: DNS Req Name: " + packet.question.name);
dns_name = packet.question.name;
}
if (scrub) {
domain_scrub(s, bytes, packet);
s.done();
} else {
s.send( to_bytes(bytes.length) );
s.send( bytes, {flush: true} );
}
} else {
if ( ! scrub) {
debug(s, "process_doh_request: DNS Req: " + line.toString() );
s.send("");
data = "";
}
}
});
}
function process_dns_request(s, decode, scrub) {
s.on("upstream", function(bytes,flags) {
if ( bytes.length == 0 ) {
return;
}
var packet;
if (bytes) {
if (s.variables.protocol == "TCP") {
// Drop the TCP length field
bytes = bytes.slice(2);
}
debug(s, "process_dns_request: DNS Req: " + bytes.toString('hex') );
if (decode) {
packet = dns.parse_packet(bytes);
debug(s, "process_dns_request: DNS Req ID: " + packet.id );
dns.parse_question(packet);
debug(s,"process_dns_request: DNS Req Name: " + packet.question.name);
dns_name = packet.question.name;
}
if (scrub) {
domain_scrub(s, bytes, packet);
s.done();
} else {
if (s.variables.protocol == "TCP") {
s.send( to_bytes(bytes.length) );
}
s.send( bytes, {flush: true} );
}
}
});
}
function domain_scrub(s, data, packet) {
var found = false;
if ( s.variables.server_port == 9953 ) {
dns_response = dns.shortcut_nxdomain(data, packet);
if (s.variables.protocol == "TCP" ) {
dns_response = Buffer.concat( [ to_bytes( dns_response.length ), dns_response ]);
}
debug(s,"Scrubbed: Response: " + dns_response.toString('hex') );
} else if ( s.variables.server_port == 9853 ) {
var answers = [];
if ( packet.question.type == dns.dns_type.A ) {
answers.push( {name: packet.question.name, type: dns.dns_type.A, class: dns.dns_class.IN, ttl: 300, rdata: "0.0.0.0" } );
} else if ( packet.question.type == dns.dns_type.AAAA ) {
answers.push( {name: packet.question.name, type: dns.dns_type.AAAA, class: dns.dns_class.IN, ttl: 300, rdata: "0000:0000:0000:0000:0000:0000:0000:0000" } );
}
dns_response = dns.shortcut_response(data, packet, answers);
if (s.variables.protocol == "TCP" ) {
dns_response = Buffer.concat( [ to_bytes( dns_response.length ), dns_response ]);
}
debug(s,"Scrubbed: Response: " + dns_response.toString('hex') );
} else {
debug(s,"Scrubbing: Check: Name: " + packet.question.name );
if ( s.variables.scrub_action ) {
debug(s, "Scrubbing: Check: EXACT MATCH: Name: " + packet.question.name + ", Action: " + s.variables.scrub_action );
dns_response = s.variables.scrub_action;
return;
} else {
["blocked", "blackhole"].forEach( function( list ) {
if(found) { return };
var blocked = s.variables[ list + "_domains" ];
if ( blocked ) {
blocked = blocked.split(',');
blocked.forEach( function( domain ) {
if (packet.question.name.endsWith( domain )) {
debug(s,"Scrubbing: Check: LISTED: Name: " + packet.question.name + ", Action: " + list );
dns_response = list;
found = true;
return;
}
});
}
});
if(found) { return };
}
debug(s,"Scrubbing: Check: NOT FOUND: Name: " + packet.question.name);
}
}
function preread_dns_request(s) {
process_dns_request(s, true, true);
}
function preread_doh_request(s) {
process_doh_request(s, true, true);
}
function filter_doh_request(s) {
if ( dns_decode_level >= 3 ) {
process_doh_request(s, true, false);
} else {
process_doh_request(s, false, false);
}
s.on("downstream", function(data, flags) {
if ( data.length == 0 ) {
return;
}
// Drop the TCP length field
data = data.slice(2);
debug(s, "DNS Res: " + data.toString('hex') );
var packet;
var answers = "";
var cache_time = 10;
if ( dns_question_balancing ) {
s.send("HTTP/1.1 200\r\nConnection: Close\r\nContent-Type: application/dns-message\r\nContent-Length:" + data.length + "\r\n");
} else {
s.send("HTTP/1.1 200\r\nConnection: Keep-Alive\r\nKeep-Alive: timeout=60, max=1000\r\nContent-Type: application/dns-message\r\nContent-Length:" + data.length + "\r\n");
}
if ( dns_decode_level > 0 ) {
packet = dns.parse_packet(data);
dns.parse_question(packet);
dns_name = packet.question.name;
s.send("X-DNS-Question: " + dns_name + "\r\n");
s.send("X-DNS-Type: " + dns.dns_type.value[packet.question.type] + "\r\n");
s.send("X-DNS-Result: " + dns.dns_codes.value[packet.codes & 0x0f] + "\r\n");
if ( dns_decode_level > 1 ) {
if ( dns_decode_level == 2 ) {
dns.parse_answers(packet, 2);
} else if ( dns_decode_level > 2 ) {
dns.parse_complete(packet, 2);
}
//debug(s, "DNS Res Answers: " + JSON.stringify( Object.entries(packet.answers)) );
if ( "min_ttl" in packet ) {
cache_time = packet.min_ttl;
s.send("X-DNS-TTL: " + packet.min_ttl + "\r\n");
}
if ( packet.an > 0 ) {
packet.answers.forEach( function(r) { answers += "[" + dns.dns_type.value[r.type] + ":" + r.rdata + "]," })
answers.slice(0,-1);
} else {
answers = "[]";
}
s.send("X-DNS-Answers: " + answers + "\r\n");
}
debug(s, "DNS Res Packet: " + JSON.stringify( Object.entries(packet)) );
}
var d = new Date( Date.now() + (cache_time*1000) ).toUTCString();
if ( ! d.includes(",") ) {
d = d.split(" ")
d = [d[0] + ',', d[2], d[1], d[3], d[4], d[5]].join(" ");
}
s.send("Cache-Control: public, max-age=" + cache_time + "\r\n" );
s.send("Expires: " + d + "\r\n" );
s.send("\r\n");
s.send( data, {flush: true} );
if ( dns_question_balancing ) {
s.done();
}
});
}
================================================
FILE: njs.d/dns/glb.js
================================================
/**
BEGIN GLB Functions
**/
import dns from "libdns.js";
export default {get_response, get_edns_subnet, process_request};
// Any encoded response packets for NGINX to send back go here
var glb_res_packet = String.bytesFrom([]);
// Client subnet gets stored in the variable if we have one
var glb_edns_subnet = String.bytesFrom([]);
// Function for js_set to use in order to pick up the glb_res_packet above
function get_response(s) {
return glb_res_packet;
}
// Function to get the EDNS subnet
function get_edns_subnet(s) {
return glb_edns_subnet;
}
// Process a DNS request and generate a response packet, saving it into glb_res_packet
function process_request(s) {
s.on("upload", function(data,flags) {
s.warn( "Received: " + data.toString('hex') );
var packet = dns.parse_packet(data);
var glb_use_edns = new Boolean(parseInt(s.variables.glb_use_edns));
s.warn( "ID: " + packet.id );
s.warn( "QD: " + packet.qd );
s.warn( "AR: " + packet.ar );
if ( packet.qd == 1 ) {
dns.parse_question(packet);
s.warn("Name: " + packet.question.name);
// Decode additional records, most clients will send an EDNS (OPT) to increase payload size
// and for EDNS Client Subnet, Cookies, etc.
if ( packet.ar > 0 ) {
// only decode if EDNS is enabled
s.warn( "USE EDNS: " + glb_use_edns );
if ( glb_use_edns ) {
dns.parse_complete(packet,1);
if ( "edns" in packet ) {
if ( packet.edns.opts.csubnet ) {
s.warn( "EDNS Subnet: " + packet.edns.opts.csubnet.subnet );
glb_edns_subnet = packet.edns.opts.csubnet.subnet;
}
}
}
}
// Check if we're doing GLB for the given name
var config = glb_get_config( packet, "", s );
if ( ! Array.isArray(config) ) {
s.warn("Failed to get config for: " + packet.question.name );
glb_res_packet = glb_failure(packet, dns.dns_codes.NXDOMAIN );
s.warn( "Sending: " + glb_res_packet.toString('hex') );
s.done();
return;
}
// GSLB this muther
var nodes = glb_get_nodes( packet, config, s );
if ( ! Array.isArray(nodes) ) {
s.warn("Failed to get any nodes for: " + packet.question.name );
glb_res_packet = glb_failure(packet, dns.dns_codes.SERVFAIL );
s.warn( "Sending: " + glb_res_packet.toString('hex') );
s.done();
return;
}
// Build an array of answers from the nodes
var answers = [];
if ( config[1] == "active" ) {
nodes.forEach( function(node) {
answers.push( {name: packet.question.name, type: dns.dns_type.A, class: dns.dns_class.IN, ttl: config[2], rdata: node} );
});
} else if ( config[1] == "random" ) {
var node = nodes[Math.floor(Math.random()*nodes.length)];
answers.push( {name: packet.question.name, type: dns.dns_type.A, class: dns.dns_class.IN, ttl: config[2], rdata: node} );
} else if ( config[1] == "geoip" ) {
var distance=99999999;
var closest = [];
var client_ip, client_lat, client_lon;
/**if ( glb_edns_subnet ) {
client_lat = s.variables.edns_latitude;
client_lon = s.variables.edns_longitude;
client_ip = glb_edns_subnet;
} else { **/
client_lat = s.variables.geoip2_latitude;
client_lon = s.variables.geoip2_longitude;
client_ip = s.variables.geoip_source;
//}
s.warn( "Client: " + client_ip + ", Lat: " + client_lat );
s.warn( "Client: " + client_ip + ", Lon: " + client_lon );
for (var i=0; i< nodes.length; i++ ) {
var suffix = "_geoip_" + nodes[i].replace(/\./g, '_');
var node_location = glb_get_config( packet, suffix, s )
if ( ! node_location ) {
s.warn( "GEO location missing. Please add GEOIP key for node: " + nodes[i] );
continue;
}
var nd = glb_calc_distance( client_lon, client_lat,
node_location[1], node_location[0]);
s.warn( "Distance to: " + nodes[i] + " - " + nd );
if ( nd < distance ) {
closest = [ nodes[i] ];
distance = nd;
} else if ( nd == distance ) {
closest.push( nodes[i] );
}
}
closest.forEach( function(node) {
answers.push( {name: packet.question.name, type: dns.dns_type.A, class: dns.dns_class.IN, ttl: config[2], rdata: node} );
});
} else {
s.warn("Unknown LB Algorithm: '" + config[1] + "' for: " + packet.question.name );
glb_res_packet = glb_failure(packet, dns.dns_codes.SERVFAIL );
s.warn( "Sending: " + glb_res_packet.toString('hex') );
s.done();
return;
}
// Shortcut - copy data from request
glb_res_packet = dns.shortcut_response(data, packet, answers);
// The long way, decode/encode
//var response = dns.gen_response_packet( packet, packet.question, answers, [], [] );
//glb_res_packet = dns.encode_packet( response );
s.warn( "Sending: " + glb_res_packet.toString('hex') );
s.done();
}
});
}
function glb_failure(packet, code) {
var failed = dns.gen_new_packet( packet.id, packet.flags, packet.codes);
failed.question = packet.question;
failed.qd = 1;
failed.codes |= code;
failed.flags |= dns.dns_flags.QR;
return dns.encode_packet( failed );
}
function glb_get_config( packet, suffix, s) {
var key = packet.question.name.replace(/\./g, '_') + suffix;
var uri = '/4/stream/keyvals/glb_config';
var config;
if ( njs.version.slice(0,3) >= 0.9 ) {
// future functionality
var db = s.api( uri );
config = db.read(key);
} else {
config = s.variables[ key ];
}
if ( config ) {
config = config.split(',');
}
return config;
}
function glb_get_nodes( packet, config, s ) {
var key = packet.question.name.replace(/\./g, '_');
var uri = "/4/" + config[0] + "/upstreams/" + key;
var nodes;
if ( njs.version.slice(0,3) >= 0.9 ) {
var db = s.api( uri );
var json = db.read(key);
nodes = glb_process_upstream_status( json, config );
} else {
// No API, so try _nodes list
nodes = s.variables[ key + "_nodes" ];
nodes = nodes.split(',');
}
return nodes;
}
function glb_process_upstream_status( json, config ) {
// TODO process upstream peers
var primary = [];
var backup = [];
}
/**
* Calculate distance between two GPS locations.
* Thanks to: https://www.barattalo.it/coding/decimal-degrees-conversion-and-distance-of-two-points-on-google-map/
**/
function glb_calc_distance(lat1,lon1,lat2,lon2) {
var R = 6371; // km (change this constant to get miles)
var dLat = (lat2-lat1) * Math.PI / 180;
var dLon = (lon2-lon1) * Math.PI / 180;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
if (d>1) return Math.round(d);
else if (d<=1) return Math.round(d*1000)+"m";
return d;
}
================================================
FILE: njs.d/dns/libdns.js
================================================
/**
BEGIN DNS Functions
**/
export default {dns_type, dns_class, dns_flags, dns_codes,
parse_packet, parse_question, parse_answers,
parse_complete, parse_resource_record,
shortcut_response, shortcut_nxdomain,
gen_new_packet, gen_response_packet, encode_packet}
// DNS Types
var dns_type = Object.freeze({
A: 1,
NS: 2,
CNAME: 5,
SOA: 6,
PTR: 12,
MX: 15,
TXT: 16,
AAAA: 28,
SRV: 33,
OPT: 41,
HTTPS: 65,
AXFR: 252,
ANY: 255,
value: { 1:"A", 2:"NS", 5:"CNAME", 6:"SOA", 12:"PTR", 15:"MX", 16:"TXT",
28:"AAAA", 33:"SRV", 41:"OPT", 65:"HTTPS", 252:"AXFR", 255:"ANY" }
});
// DNS Classes
var dns_class = Object.freeze({
IN: 1,
CS: 2,
CH: 3,
HS: 4,
value: { 1:"IN", 2:"CS", 3:"CH", 4:"HS" }
});
// DNS flags (made up of QR, Opcode (4bits), AA, TrunCation, Recursion Desired)
var dns_flags = Object.freeze({
QR: 0x80,
AA: 0x4,
TC: 0x2,
RD: 0x1
});
// DNS Codes (made up of RA (Recursion Available), Zero (3bits), Response Code (4bits))
var dns_codes = Object.freeze({
RA: 0x80,
Z: 0x70,
//RCODE: 0xf,
NOERROR: 0x0,
FORMERR: 0x1,
SERVFAIL: 0x2,
NXDOMAIN: 0x3,
NOTIMPL: 0x4,
REFUSED: 0x5,
value: { 0x80:"RA", 0x70:"Z", 0x0:"NOERROR", 0x1:"FORMERR", 0x2:"SERVFAIL", 0x3:"NXDOMAIN", 0x4:"NOTIMPL", 0x5:"REFUSED" }
});
// Encode the given number to two bytes (16 bit)
function to_bytes( number ) {
return Buffer.from( [ ((number>>8) & 0xff), (number & 0xff) ] );
}
// Encode the given number to 4 bytes (32 bit)
function to_bytes32( number ) {
return Buffer.from( [ (number>>24)&0xff, (number>>16)&0xff, (number>>8)&0xff, number&0xff ] );
}
// Create a new empty DNS packet structure
function gen_new_packet(id, flags, codes) {
var dns_packet = { id: id, flags: flags, codes: codes, qd: 0, an: 0, ns: 0, ar: 0,
question: {},
answers: [],
authority: [],
additional: []
};
return dns_packet;
}
/** Create a new response packet suitable as a reply to the given request
* You should also supply some answers, authority and/or additional records
* in arrays to populate the various sections.
**/
function gen_response_packet( request, question, answers, authority, additional ) {
var response = gen_new_packet(request.id, request.flags, request.codes);
response.flags |= dns_flags.AA + dns_flags.QR;
response.codes |= dns_codes.RA;
if ( question == null ) {
response.qd = 0;
} else {
response.qd = 1;
response.question = request.question;
}
answers.forEach( function(answer) {
response.an++;
response.answers.push( answer );
});
return response;
}
/** Encode the provided packet, converting it from the javascript object structure into a bytestring
* Returns a bytestring suitable for dropping into a UDP packet, or returning to NGINX
**/
function encode_packet( packet ) {
var encoded = Buffer.from( to_bytes( packet.id ) );
encoded = Buffer.concat( [ encoded, Buffer.from([ packet.flags ])] );
encoded = Buffer.concat( [ encoded, Buffer.from([ packet.codes ])] );
encoded = Buffer.concat( [ encoded, Buffer.from( to_bytes( packet.qd ))] ); // Questions
encoded = Buffer.concat( [ encoded, Buffer.from( to_bytes( packet.answers.length ))] ); // Answers
encoded = Buffer.concat( [ encoded, Buffer.from( to_bytes( packet.authority.length ))] ); // Authority
encoded = Buffer.concat( [ encoded, Buffer.from( to_bytes( packet.additional.length ))] ); // Additional
encoded = Buffer.concat( [ encoded, encode_question(packet) ]);
packet.answers.forEach( function(answer) {
encoded = Buffer.concat( [ encoded, gen_resource_record(packet, answer.name, answer.type, answer.class, answer.ttl, answer.rdata) ]);
});
packet.authority.forEach( function(auth) {
encoded = Buffer.concat( [ encoded, gen_resource_record(packet, auth.name, auth.type, auth.class, auth.ttl, auth.rdata)] );
});
packet.additional.forEach( function(adtnl) {
encoded = Buffer.concat( [ encoded, gen_resource_record(packet, adtnl.name, adtnl.type, adtnl.class, adtnl.ttl, adtnl.rdata)] );
});
return encoded;
}
/** Don't mess about. This is a shortcut for responding to DNS Queries. We copy the question out of the query
* and cannibalise the original request to generate our response.
**/
function shortcut_response(data, packet, answers) {
var response = Buffer.alloc(0);
response = Buffer.concat( [ response, data.slice(0,2) ] );
response = Buffer.concat( [ response, Buffer.from([ (packet.flags |= dns_flags.AA | dns_flags.QR) ])] );
response = Buffer.concat( [ response, Buffer.from([ (packet.codes |= dns_codes.RA) ])] );
// append counts: qd, answer count, 0 auths, 0 additional
response = Buffer.concat( [ response, Buffer.from([ 0x00, 0x01 ]), Buffer.from( to_bytes(answers.length)), Buffer.from( [0x0, 0x0, 0x0, 0x0 ]) ] );
response = Buffer.concat( [ response, data.slice(12, packet.question.qend ) ] );
answers.forEach( function(answer) {
response = Buffer.concat( [ response, gen_resource_record(packet, answer.name, answer.type, answer.class, answer.ttl, answer.rdata) ]);
});
return response;
}
function shortcut_nxdomain(data, packet) {
var response = Buffer.alloc(0);
response = Buffer.concat( [ response, data.slice(0,2) ] );
response = Buffer.concat( [ response, Buffer.from([ (packet.flags |= dns_flags.AA | dns_flags.QR) ])] );
response = Buffer.concat( [ response, Buffer.from([ (packet.codes |= dns_codes.NXDOMAIN | dns_codes.RA) ])] );
// append counts: qd, answer count, 0 auths, 0 additional
response = Buffer.concat( [ response, Buffer.from([ 0x00, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]) ] );
response = Buffer.concat( [ response, data.slice(12, packet.question.qend ) ] );
return response;
}
/** Encode a question object into a bytestring suitable for use in a UDP packet
**/
function encode_question(packet) {
var encoded = Buffer.from( encode_label(packet.question.name) );
encoded = Buffer.concat( [ encoded, Buffer.from(to_bytes(packet.question.type)), Buffer.from(to_bytes(packet.question.class)) ] );
return encoded;
}
/**
* Parse an incoming request bytestring into a DNS packet object. This function decodes the first 12 bytes of the headers.
* You will probably want to call parse_question() next.
**/
function parse_packet(data) {
var packet = { id: data.readUInt16BE(0), flags: data[2], codes: data[3], min_ttl: 2147483647,
qd: data.readUInt16BE(4), an: data.readUInt16BE(6), ns: data.readUInt16BE(8),
ar: data.readUInt16BE(10), data: data.slice(12), question: [], answers:[], authority: [], additional: [], offset: 0 };
return packet;
}
/**
* Parse the question section of a DNS request packet, adds the QNAME, QTYPE, and QCLASS to the packet object, and stores the
* offset in the packet for processing any further sections.
**/
function parse_question(packet) {
/** QNAME, QTYPE, QCLASS **/
var name = parse_label(packet);
packet.question = { name: name, type: packet.data.readUInt16BE(packet.offset),
class: packet.data.readUInt16BE(packet.offset+2), qend: packet.offset + 16 };
packet.offset += 4;
if ( packet.qd != 1 ) {
return false;
}
return true;
}
function parse_answers(packet, decode_level) {
// Process the question section if necessary
if ( packet.question.length == 0 ) {
parse_question(packet);
}
// Process answers
if ( packet.an > 0 && packet.answers.length == 0 ) {
packet.answers = parse_section(packet, packet.an, decode_level);
}
// If we didn't have any ttls in the packet, then cache for 5 minutes.
if (packet.min_ttl == 2147483647) {
packet.min_ttl = 300;
}
}
// Parse all sections of the packet
function parse_complete(packet, decode_level) {
// Process the question section if necessary
if ( packet.question.length == 0 ) {
parse_question(packet);
}
// Process answers
if ( packet.an > 0 && packet.answers.length == 0 ) {
packet.answers = parse_section(packet, packet.an, decode_level);
}
// Process authority
if ( packet.ns > 0 && packet.authority.length == 0) {
packet.authority = parse_section(packet, packet.ns, decode_level);
}
// Process Additional
if ( packet.ar > 0 && packet.additional.length == 0) {
packet.additional = parse_section(packet, packet.ar, decode_level);
}
// If we didn't have any ttls in the packet, then cache for 5 minutes.
if (packet.min_ttl == 2147483647) {
packet.min_ttl = 300;
}
}
function parse_section(packet, recs, decode_level) {
var rrs = [];
for (var i=0; i<recs; i++) {
var rec = parse_resource_record(packet, decode_level);
rrs.push(rec);
if ( rec.ttl < packet.min_ttl ) {
packet.min_ttl = rec.ttl;
}
}
return rrs;
}
function parse_label(packet) {
var name = "";
var compressed = false;
var pos = packet.offset;
for ( ; pos < packet.data.length; ) {
var length = packet.data[pos];
if (length == 0) {
// null label, name is finished
pos++;
break;
} else if ( length == 192 ) {
// compression pointer
if ( compressed ) {
pos++;
} else {
packet.offset = ++pos + 1;
}
pos = packet.data[pos];;
if ( pos < 12 ) {
// This shouldn't be possible, the header is 12 bytes so a compression pointer can't be less than 12
//s.warn("DNS Error - parse_label encountered impossible compression pointer");
break;
} else {
pos = pos - 12;
compressed = true;
}
} else if ( length > 63 ) {
// Invalid DNS name, individual labels are limited to 63 bytes.
//s.warn("DNS Error - parse_label encountered invaliad DNS name");
break;
} else {
name += packet.data.slice(++pos, pos+length) + ".";
pos += length;
}
}
if ( ! compressed ) {
packet.offset = pos
}
name = name.slice(0,-1);
return name;
}
/** TODO Check sizes on resources/packets
labels 63 octets or less
names 255 octets or less
TTL positive values of a signed 32 bit number.
UDP messages 512 octets or less
**/
function encode_label( name ) {
var data = Buffer.alloc(0);
name.split('.').forEach( function(part){
data = Buffer.concat( [ data, Buffer.from([ part.length ]), Buffer.from(part) ] );
});
data = Buffer.concat( [data, Buffer.from([0]) ]);
return data;
}
function gen_resource_record(packet, name, type, clss, ttl, rdata) {
/**
NAME
TYPE (2 octets)
CLASS (2 octects)
TTL 32bit signed int
RDLength 16bit int length of RDATA
RDATA variable length string
**/
var resource
var record = "";
if ( name == packet.question.name ) {
// The name matches the query, set a compression pointer.
resource = Buffer.from([192, 12]);
} else {
// gen labels for the name
resource = encode_label(name);
}
resource = Buffer.concat( [ resource, Buffer.from([ type & 0xff00, type & 0xff ]) ]);
switch(type) {
case dns_type.A:
record = encode_arpa_v4(rdata);
break;
case dns_type.AAAA:
record = encode_arpa_v6(rdata);
break;
case dns_type.NS:
record = encode_label(rdata);
break;
case dns_type.CNAME:
record = encode_label(rdata);
break;
case dns_type.SOA:
record = encode_soa_record(rdata);
break;
case dns_type.SRV:
record = encode_srv_record(rdata);
break;
case dns_type.MX:
record = encode_mx_record(rdata);
break;
case dns_type.TXT:
record = encode_txt_record(rdata);
break;
default:
//TODO Barf
}
switch(clss) {
case dns_class.IN:
resource = Buffer.concat([ resource, Buffer.from( [ 0, 1 ] )]);
break;
default:
//TODO Barf
resource = Buffer.concat([ resource, Buffer.from( [ 99, 99 ] )]);
}
resource = Buffer.concat( [ resource, Buffer.from(to_bytes32(ttl)) ] );
resource = Buffer.concat( [ resource, Buffer.from(to_bytes( record.length )) ] );
resource = Buffer.concat( [ resource, Buffer.from(record) ] );
return resource;
}
// Process resource records, to a varying depth dictated by decode_level
// decode_level {0: name+type, 1: name+type+class+ttl, 2: everything}
function parse_resource_record(packet, decode_level) {
/**
NAME
TYPE (2 octets)
CLASS (2 octects)
TTL 32bit signed int
RDLength 16bit int length of RDATA
RDATA variable length string
**/
var resource = {}
resource.name = parse_label(packet);
resource.type = packet.data.readUInt16BE(packet.offset);
packet.offset += 2;
if ( decode_level > 0 ) {
if (resource.type == dns_type.OPT ) {
// EDNS
parse_edns_options(packet);
} else {
resource.class = packet.data.readUInt16BE(packet.offset);
resource.ttl = packet.data.readUInt32BE(packet.offset+2);
resource.rdlength = packet.data.readUInt16BE(packet.offset+6);
packet.offset +=8;
if ( decode_level == 1 ) {
resource.rdata = packet.data.slice(packet.offset, packet.offset + resource.rdlength);
packet.offset += resource.rdlength;
} else {
switch(resource.type) {
case dns_type.A:
resource.rdata = parse_arpa_v4(packet, resource);
break;
case dns_type.AAAA:
resource.rdata = parse_arpa_v6(packet, resource);
break;
case dns_type.NS:
resource.rdata = parse_label(packet);
break;
case dns_type.CNAME:
resource.rdata = parse_label(packet);
break;
case dns_type.SOA:
resource.rdata = parse_soa_record(packet);
break;
case dns_type.SRV:
resource.rdata = parse_srv_record(packet);
break;
case dns_type.MX:
resource.rdata = parse_mx_record(packet);
break;
case dns_type.TXT:
resource.rdata = parse_txt_record(packet, resource.rdlength);
break;
default:
resource.rdata = packet.data.slice(packet.offset, packet.offset + resource.rdlength);
packet.offset += resource.rdlength;
}
}
}
}
return resource;
}
function encode_arpa_v4( ipv4 ) {
var rdata = Buffer.alloc(4);
var index = 0;
ipv4.split('\.').forEach( function(octet) {
rdata[index++] = octet;
});
return rdata;
}
function parse_arpa_v4(packet) {
var octet = [0,0,0,0];
for (var i=0; i< 4 ; i++ ) {
octet[i] = packet.data[packet.offset++];
}
return octet.join(".");
}
function encode_arpa_v6( ipv6 ) {
var rdata = Buffer.alloc(0);
ipv6.split(':').forEach( function(segment) {
rdata = Buffer.concat( [ rdata, Buffer.from( segment[0] + segment[1], 'hex') ] );
rdata = Buffer.concat( [ rdata, Buffer.from( segment[2] + segment[3], 'hex') ] );
});
return rdata;
}
function parse_arpa_v6(packet) {
var ipv6 = "";
for (var i=0; i<8; i++ ) {
ipv6 += packet.data.toString('hex', packet.offset++, ++packet.offset) + ":";
}
return ipv6.slice(0,-1);
}
function encode_txt_record( text_array ) {
var rdata = Buffer.alloc(0);
text_array.forEach( function(text) {
var tl = text.length;
if ( tl > 255 ) {
for (var i=0 ; i < tl ; i++ ) {
var len = (tl > (i+255)) ? 255 : tl - i;
rdata = Buffer.concat( [ rdata, Buffer.from([len]), Buffer.from(text.slice(i,i+len)) ] );
i += len;
}
} else {
rdata = Buffer.concat( [ rdata, Buffer.from([tl]), Buffer.from(text) ] );
}
});
return rdata;
}
function parse_txt_record(packet, length) {
var txt = [];
var pos = 0;
while ( pos < length ) {
var tl = packet.data[packet.offset++];
txt.push( packet.data.toString('utf8', packet.offset, packet.offset + tl));
pos += tl + 1;
packet.offset += tl;
}
return txt;
}
function encode_mx_record( mx ) {
var rdata = Buffer.alloc(0);
rdata += to_bytes( mx.priority );
rdata += encode_label( mx.exchange );
return rdata;
}
function parse_mx_record(packet) {
var mx = {};
mx.priority = packet.data.readUInt16BE(packet.offset);
packet.offset += 2;
mx.exchange = parse_label(packet);
return mx;
}
function encode_srv_record( srv ) {
var rdata = Buffer.alloc(6)
rdata.writeInt16BE( srv.priority, 0 );
rdata.writeInt16BE( srv.weight, 2 );
rdata.writeInt16BE( srv.port, 4 );
rdata = Buffer.concat( [ rdata, encode_label( srv.target ) ]);
ngx.log( ngx.WARN, rdata.toString('hex'));
return rdata;
}
function parse_srv_record(packet) {
var srv = {};
srv.priority = packet.data.readUInt16BE(packet.offset);
srv.weight = packet.data.readUInt16BE(packet.offset+2);
srv.port = packet.data.readUInt16BE(packet.offset+4);
packet.offset += 6;
srv.target = parse_label(packet);
return srv;
}
function encode_soa_record( soa ) {
var rdata = Buffer.concat([ encode_label(soa.primary), encode_label(soa.mailbox) ]);
rdata = Buffer.concat( [ rdata, Buffer.from(to_bytes32(soa.serial)), Buffer.from(to_bytes32(soa.refresh)),
Buffer.from(to_bytes32(soa.retry)), Buffer.from(to_bytes32(soa.expire)), Buffer.from(to_bytes32(soa.minTTL)) ]);
return rdata;
}
function parse_soa_record(packet) {
var soa = {};
soa.primary = parse_label(packet);
soa.mailbox = parse_label(packet);
soa.serial = packet.data.readUInt32BE(packet.offset);
soa.refresh = packet.data.readUInt32BE(packet.offset+=4);
soa.retry = packet.data.readUInt32BE(packet.offset+=4);
soa.expire = packet.data.readUInt32BE(packet.offset+=4);
soa.minTTL = packet.data.readUInt32BE(packet.offset+=4);
packet.offset +=4;
return soa;
}
function parse_edns_options(packet) {
packet.edns = {}
packet.edns.opts = {}
packet.edns.size = packet.data.readUInt16BE(packet.offset);
packet.edns.rcode = packet.data[packet.offset+2];
packet.edns.version = packet.data[packet.offset+3];
packet.edns.z = packet.data.readUInt16BE(packet.offset+4);
packet.edns.rdlength = packet.data.readUInt16BE(packet.offset+6);
packet.offset += 8;
var end = packet.offset + packet.edns.rdlength;
for ( ; packet.offset < end ; ) {
var opcode = packet.data.readUInt16BE(packet.offset);
var oplength = packet.data.readUInt16BE(packet.offset+2);
packet.offset += 4;
if ( opcode == 8 ) {
//client subnet
packet.edns.opts.csubnet = {}
packet.edns.opts.csubnet.family = packet.data.readUInt16BE(packet.offset);
packet.edns.opts.csubnet.netmask = packet.data[packet.offset+2];
packet.edns.opts.csubnet.scope = packet.data[packet.offset+3];
packet.offset += 4;
if ( packet.edns.opts.csubnet.family == 1 ) {
// IPv4
var octet = [0,0,0,0];
for (var i=4; i< oplength ; i++ ) {
octet[i-4] = packet.data[packet.offset++];
}
packet.edns.opts.csubnet.subnet = octet.join(".");
break;
} else {
// We don't support IPv6 yet.
packet.edns.opts = {}
break;
}
} else {
// We only look for CSUBNET... Not interested in anything else at this time.
packet.offset += oplength;
}
}
}
================================================
FILE: njs.d/dns/test.js
================================================
import dns from "libdns.js";
export default {get_qname, test_dns_encoder, test_dns_decoder};
/**
* DNS Decode Level
* 0: No decoding, minimal processing required to strip packet from HTTP wrapper (fastest)
* 1: Parse DNS Header and Question. We can log the Question, Class, Type, and Result Code
* 2: As 1, but also parse answers. We can log the answers, and also cache responses in HTTP Content-Cache
* 3: Very Verbose, log everything as above, but also write packet data to error log (slowest)
**/
var dns_decode_level = 3;
/**
* DNS Debug Level
* Specify the decoding level at which we should log packet data to the error log.
* Default is level 3 (max decoding)
**/
var dns_debug_level = 3;
// The DNS Question name
var dns_name = Buffer.alloc(0);
function get_qname(s) {
return dns_name;
}
// Encode the given number to two bytes (16 bit)
function to_bytes( number ) {
return Buffer.from( [ ((number>>8) & 0xff), (number & 0xff) ] );
}
function debug(s, msg) {
if ( dns_decode_level >= dns_debug_level ) {
s.warn(msg);
}
}
function test_dns_encoder(s) {
s.on("upstream", function(data,flags) {
var packet;
var test_result = Buffer.alloc(0);
if ( data.length == 0 ) {
return;
}
if (data) {
if (s.variables.protocol == "TCP") {
// Drop the TCP length field
data = data.slice(2);
}
debug(s, "test_dns: DNS Encoder Req: " + data.toString('hex') );
packet = dns.parse_packet(data);
dns.parse_question(packet);
dns_name = packet.question.name;
debug(s, "test_dns: DNS Encoder Request Packet: " + JSON.stringify( Object.entries(packet)) );
test_result = test_dns_responder(s, data, packet);
delete packet.data; // remove the data buffer before printing
debug(s, "test_dns: DNS Encoder Response Packet: " + JSON.stringify( Object.entries(packet)) );
debug(s, "test_dns: DNS Encoder Res: " + test_result.toString('hex') );
s.variables.test_result = test_result;
s.done();
}
});
}
function test_dns_decoder(s) {
s.on("downstream", function(data,flags) {
var packet;
var test_result = Buffer.alloc(0);
if ( data.length == 0 ) {
return;
}
if (data) {
if (s.variables.protocol == "TCP") {
// Drop the TCP length field
data = data.slice(2);
}
debug(s, "test_dns: DNS Decoder Res: " + data.toString('hex') );
packet = dns.parse_packet(data);
dns.parse_question(packet);
dns_name = packet.question.name;
dns.parse_complete(packet, 2);
delete packet.data; // remove the data buffer before printing
debug(s, "test_dns: DNS Decoder Response Packet: " + JSON.stringify( Object.entries(packet)) );
if (s.variables.protocol == "TCP") {
s.send( to_bytes(data.length) );
}
s.send( data, {flush: true} );
}
});
}
/**
* Function to perform testing of DNS packet generation for various DNS types
* Any domain ending bar.com will use the shortcut_response path
* Any domains ending baz.com will use shortcut_nxdomain path
* All other queries will return an appropriate set of DNS records.
**/
function test_dns_responder(s, data, packet) {
var answers = [];
var test_result;
if ( packet.question.type == dns.dns_type.A || packet.question.type == dns.dns_type.ANY ) {
answers.push( {name: packet.question.name, type: dns.dns_type.A, class: dns.dns_class.IN, ttl: 300, rdata: "10.2.3.4" } );
} else if ( packet.question.type == dns.dns_type.AAAA ) {
answers.push( {name: packet.question.name, type: dns.dns_type.AAAA, class: dns.dns_class.IN, ttl: 300, rdata: "fe80:0002:0003:0004:0005:0006:0007:0008" } );
} else if ( packet.question.type == dns.dns_type.CNAME ) {
answers.push( {name: packet.question.name, type: dns.dns_type.CNAME, class: dns.dns_class.IN, ttl: 300, rdata: "www.foo.bar.baz" } );
} else if ( packet.question.type == dns.dns_type.NS ) {
answers.push( {name: packet.question.name, type: dns.dns_type.NS, class: dns.dns_class.IN, ttl: 300, rdata: "ns1.foo.bar.baz" } );
answers.push( {name: packet.question.name, type: dns.dns_type.NS, class: dns.dns_class.IN, ttl: 300, rdata: "ns2.foo.bar.baz" } );
} else if ( packet.question.type == dns.dns_type.TXT ) {
answers.push( {name: packet.question.name, type: dns.dns_type.TXT, class: dns.dns_class.IN, ttl: 300, rdata: ["ns1.foo.bar.baz","1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890"] } );
} else if ( packet.question.type == dns.dns_type.MX ) {
answers.push( {name: packet.question.name, type: dns.dns_type.MX, class: dns.dns_class.IN, ttl: 300, rdata: { priority: 1, exchange: "mx1.foo.com"} } );
answers.push( {name: packet.question.name, type: dns.dns_type.MX, class: dns.dns_class.IN, ttl: 300, rdata: { priority: 10, exchange: "mx2.foo.com"} } );
} else if ( packet.question.type == dns.dns_type.SRV ) {
answers.push( {name: packet.question.name, type: dns.dns_type.SRV, class: dns.dns_class.IN, ttl: 300, rdata: { priority: 1, weight: 10, port: 443, target: "server1.foo.com"} } );
} else if ( packet.question.type == dns.dns_type.SOA ) {
answers.push( {name: packet.question.name, type: dns.dns_type.SOA, class: dns.dns_class.IN, ttl: 300, rdata: { primary: "ns1.foo.com", mailbox: "mb.nginx.com", serial: 2019102801, refresh: 1800, retry: 3600, expire: 826483, minTTL:300} } );
}
if ( packet.question.name.toString().endsWith("bar.com") ) {
test_result = dns.shortcut_response(data, packet, answers);
} else if ( packet.question.name.toString().endsWith("baz.com") ) {
test_result = dns.shortcut_nxdomain(data, packet);
} else {
packet.flags |= dns.dns_flags.AA | dns.dns_flags.QR;
packet.codes |= dns.dns_codes.RA;
packet.authority.push( {name: packet.question.name, type: dns.dns_type.SOA, class: dns.dns_class.IN, ttl: 300, rdata: { primary: "ns1.foo.com", mailbox: "mb.nginx.com", serial: 2019102801, refresh: 1800, retry: 3600, expire: 826483, minTTL:300} });
packet.additional.push( {name: packet.question.name, type: dns.dns_type.NS, class: dns.dns_class.IN, ttl: 300, rdata: "ns1.foo.bar.baz" } );
packet.additional.push( {name: packet.question.name, type: dns.dns_type.NS, class: dns.dns_class.IN, ttl: 300, rdata: "ns2.foo.bar.baz" } );
packet.answers = answers;
test_result = dns.encode_packet(packet);
}
if (s.variables.protocol == "TCP" ) {
test_result = Buffer.concat( [ to_bytes( test_result.length ), test_result ]);
}
return test_result;
}
================================================
FILE: ssl/certs/doh.local.pem
================================================
-----BEGIN CERTIFICATE-----
MIID0TCCArmgAwIBAgIUOdQrJG61Cs5p1PRwIOYzUClAZZkwDQYJKoZIhvcNAQEL
BQAweDELMAkGA1UEBhMCR0IxEjAQBgNVBAgMCUNhbWJyaWRnZTESMBAGA1UEBwwJ
Q2FtYnJpZGdlMRIwEAYDVQQKDAlOR0lOWCBJbmMxGTAXBgNVBAsMEE5vdyBhIHBh
cnQgb2YgRjUxEjAQBgNVBAMMCWRvaC5sb2NhbDAeFw0xOTA5MjgxNDU2MzNaFw0y
MDA5MjcxNDU2MzNaMHgxCzAJBgNVBAYTAkdCMRIwEAYDVQQIDAlDYW1icmlkZ2Ux
EjAQBgNVBAcMCUNhbWJyaWRnZTESMBAGA1UECgwJTkdJTlggSW5jMRkwFwYDVQQL
DBBOb3cgYSBwYXJ0IG9mIEY1MRIwEAYDVQQDDAlkb2gubG9jYWwwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSnQpcd9PElrimhx1Absbf4SafKPpM+7Nh
EVBFJ5Emtxksz1tUsi1mXEQsf9sCKeURvrzoUwyUkkF4Frks14L/+GCXEpCJSrga
NhhSO6QR0xZ26jXFqwwsE3QkW6URNGZ5IEecI+2JAUiMxhdmO9oEPvRzDmyDoUTT
dmt6+y0NahrU47OP88yie0Jt1+Mh18U/RQKRUYZz1L4oHV1sujuemDbF7xSkguvV
EhUMF+316HQNPndrZRVIYjfMUT32qnvlnOKzgB4mNh8biRLekwsPplFuU5vhnUxR
5pDw4JzNT5Mis8I8+ULUkKKK3wF7Ih3Wp6vMgq/i6CvqcYA/n8LzAgMBAAGjUzBR
MB0GA1UdDgQWBBSFF5wbFm0N3FXIu14wGAfHIIEDyjAfBgNVHSMEGDAWgBSFF5wb
Fm0N3FXIu14wGAfHIIEDyjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
A4IBAQCHIjdUCNKHnk06jyajT8rKMdjpJQkv6P2nFr0Sf0/1ZtftK+dgA5O3HJmY
aPJVGZlfdOWavYT3i+OrLpSVCwGoCt/V1rSgw6E9zfEarsVdtiZzd9h/HhvOdDGd
SE1EUJveIoe46DpdeD+pSz068+1WKK7UahArupsXjlcaoCVp7uvLTacP9NPMP6jp
aiOotgZUxHEoelseEgyFGDOyP32OTZv8vGQFTaNLS3zKP8iZNNmfwX1pirY5TJzP
HcbKgT8aki9+U64vkjoUrvpA8y4U8b7NgKFowkLl7rbHxNqZmstq+YI13RwgNcYG
kEbvwju0TuQ52TgpKkA3D5fiWhSZ
-----END CERTIFICATE-----
================================================
FILE: ssl/private/doh.local.pem
================================================
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSnQpcd9PElrim
hx1Absbf4SafKPpM+7NhEVBFJ5Emtxksz1tUsi1mXEQsf9sCKeURvrzoUwyUkkF4
Frks14L/+GCXEpCJSrgaNhhSO6QR0xZ26jXFqwwsE3QkW6URNGZ5IEecI+2JAUiM
xhdmO9oEPvRzDmyDoUTTdmt6+y0NahrU47OP88yie0Jt1+Mh18U/RQKRUYZz1L4o
HV1sujuemDbF7xSkguvVEhUMF+316HQNPndrZRVIYjfMUT32qnvlnOKzgB4mNh8b
iRLekwsPplFuU5vhnUxR5pDw4JzNT5Mis8I8+ULUkKKK3wF7Ih3Wp6vMgq/i6Cvq
cYA/n8LzAgMBAAECggEABDnYcmCJJEGt9NFzOc6/ONDIuJrW4uKOB92UEb8of3Ff
FPIYMAvfM1WYnJf4KgPzL7b3DWZVM0n3/FPgZVDxtPcj4QQjWE3igcwiEsxVj3H/
2mT6rTuwY9YEF5KrLjwx7i5CoZRq+LvI2+JBp/B9gGZO+1wHu2BqBCA1KeOOVN2J
2FDLt32sMLrY4QQqFefYGk3gdzD06qyjRbpSiymaDOfK7D1xr6iNqGZHOl8eu8I4
zHBVwuE9L3qPGY+bH1rTWSBrV/hIJSKhXom4PbNz23KpfrgpGJ+yP8hspEUgtGiH
0Et9o59zKju77sLkZvhLOQSy8Y/yCpn9qNLqoNl8QQKBgQDs4lXCYkaQNMErGYpf
2+T7fRyuRpCedmoM70p1EmwtLi2ufIWPWPr+7NpvIdmXZb02rzE5gMMAROmC0c7b
9n7jqBMN8LjdG2yeyl0a1rxxE/eMrYqZ/SIEaxUnam7B50Re767/uUL8Uq2UOuDi
9e1g6LzvXixM7m1JpS4pkrDkYQKBgQDjm/5DqmOD/cfLbpNhxnLiQkJEHt553+mh
xXWaxugBGjRyFlXfaRNAoJa5D7+VRc+iFNPF9CwYYMV93JYbVzNwiR5HJ7GfYRKZ
+01NN5gAZwsOdhiqhkxjc9PcP9JrcGnGC9RMUtSvscwNPh3R2DKPHpnz3O96yzPG
qZqpEcDn0wKBgQCdX0RgLk/4r8OBMaeXRYwbc6PhN+oODFcqHrMVkdaiMWKR4BIP
CKs/PvVjDVb0WNfag4stS5jBDgcgLOjDg0ALWHbINRtrcTO5TnGKSgzJBt3X7Nb+
tIer7cQQ+ol4cn8enxdgtqCE5xyANJmAzqcUUaprT+IYffHHEmDXp6ezIQKBgQCL
+3prPzWpDcF8+eqmrZgmUz3SC3IkXnOfzINBx6cUVnt+1wHFPyhaDOnlsyvOsHq8
YjbEfiFIdOvBNpMTCZRXV91JQb5aGSeJkCbAoLpZNQZ1xGfzKFl+qNPZl17gOOi0
pr3Qmvi3fY/TbSqFzoN5xgZFFtIqISMcwV6fMI4FhQKBgGLoMW/OK69zAJpwsVV1
AjVfXzEY3UtK8RVWspl+bOKPArIKvtR4aTvcT2kkYMxjXM+0wm7dfQq1a3gvyv/K
d0mhr/Sst7OZuMcWH7xwM5ZmvPWnEMYh55BNThKZ7gdJ6+SFNOWNDCzLao1sC+uX
GpBWCMEPmsLEVsth20BRtr90
-----END PRIVATE KEY-----
gitextract_h3c4240l/
├── LICENSE
├── README.md
├── docs/
│ └── nginx-dns-over-https.md
├── examples/
│ ├── nginx-dns-routing.conf
│ ├── nginx-dns-simple.conf
│ ├── nginx-doh-and-dot-to-dns.conf
│ ├── nginx-doh-and-dot-to-dot.conf
│ ├── nginx-dot-to-dns-simple.conf
│ ├── nginx-dot-to-dot-routing.conf
│ ├── nginx-plus-filtering.conf
│ └── test.conf
├── nginx-doh.conf
├── nginx-glb.conf
├── njs.d/
│ └── dns/
│ ├── dns.js
│ ├── glb.js
│ ├── libdns.js
│ └── test.js
└── ssl/
├── certs/
│ └── doh.local.pem
└── private/
└── doh.local.pem
SYMBOL INDEX (54 symbols across 4 files)
FILE: njs.d/dns/dns.js
function get_qname (line 30) | function get_qname(s) {
function get_response (line 37) | function get_response(s) {
function to_bytes (line 42) | function to_bytes( number ) {
function debug (line 46) | function debug(s, msg) {
function process_doh_request (line 52) | function process_doh_request(s, decode, scrub) {
function process_dns_request (line 112) | function process_dns_request(s, decode, scrub) {
function domain_scrub (line 144) | function domain_scrub(s, data, packet) {
function preread_dns_request (line 192) | function preread_dns_request(s) {
function preread_doh_request (line 196) | function preread_doh_request(s) {
function filter_doh_request (line 200) | function filter_doh_request(s) {
FILE: njs.d/dns/glb.js
function get_response (line 17) | function get_response(s) {
function get_edns_subnet (line 22) | function get_edns_subnet(s) {
function process_request (line 27) | function process_request(s) {
function glb_failure (line 140) | function glb_failure(packet, code) {
function glb_get_config (line 149) | function glb_get_config( packet, suffix, s) {
function glb_get_nodes (line 166) | function glb_get_nodes( packet, config, s ) {
function glb_process_upstream_status (line 182) | function glb_process_upstream_status( json, config ) {
function glb_calc_distance (line 192) | function glb_calc_distance(lat1,lon1,lat2,lon2) {
FILE: njs.d/dns/libdns.js
function to_bytes (line 64) | function to_bytes( number ) {
function to_bytes32 (line 69) | function to_bytes32( number ) {
function gen_new_packet (line 74) | function gen_new_packet(id, flags, codes) {
function gen_response_packet (line 88) | function gen_response_packet( request, question, answers, authority, add...
function encode_packet (line 108) | function encode_packet( packet ) {
function shortcut_response (line 132) | function shortcut_response(data, packet, answers) {
function shortcut_nxdomain (line 146) | function shortcut_nxdomain(data, packet) {
function encode_question (line 159) | function encode_question(packet) {
function parse_packet (line 169) | function parse_packet(data) {
function parse_question (line 180) | function parse_question(packet) {
function parse_answers (line 194) | function parse_answers(packet, decode_level) {
function parse_complete (line 215) | function parse_complete(packet, decode_level) {
function parse_section (line 244) | function parse_section(packet, recs, decode_level) {
function parse_label (line 256) | function parse_label(packet) {
function encode_label (line 308) | function encode_label( name ) {
function gen_resource_record (line 319) | function gen_resource_record(packet, name, type, clss, ttl, rdata) {
function parse_resource_record (line 388) | function parse_resource_record(packet, decode_level) {
function encode_arpa_v4 (line 452) | function encode_arpa_v4( ipv4 ) {
function parse_arpa_v4 (line 461) | function parse_arpa_v4(packet) {
function encode_arpa_v6 (line 469) | function encode_arpa_v6( ipv6 ) {
function parse_arpa_v6 (line 478) | function parse_arpa_v6(packet) {
function encode_txt_record (line 486) | function encode_txt_record( text_array ) {
function parse_txt_record (line 503) | function parse_txt_record(packet, length) {
function encode_mx_record (line 515) | function encode_mx_record( mx ) {
function parse_mx_record (line 522) | function parse_mx_record(packet) {
function encode_srv_record (line 530) | function encode_srv_record( srv ) {
function parse_srv_record (line 540) | function parse_srv_record(packet) {
function encode_soa_record (line 550) | function encode_soa_record( soa ) {
function parse_soa_record (line 557) | function parse_soa_record(packet) {
function parse_edns_options (line 570) | function parse_edns_options(packet) {
FILE: njs.d/dns/test.js
function get_qname (line 24) | function get_qname(s) {
function to_bytes (line 29) | function to_bytes( number ) {
function debug (line 33) | function debug(s, msg) {
function test_dns_encoder (line 39) | function test_dns_encoder(s) {
function test_dns_decoder (line 67) | function test_dns_decoder(s) {
function test_dns_responder (line 100) | function test_dns_responder(s, data, packet) {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (81K chars).
[
{
"path": "LICENSE",
"chars": 1325,
"preview": "BSD 2-Clause License\n\nCopyright (c) 2022, Mark Boddington\nAll rights reserved.\n\nRedistribution and use in source and bin"
},
{
"path": "README.md",
"chars": 3666,
"preview": "# NGINX DNS (DNS/DoT/DoH)\n\n> The `v2` branch is migrating to `Buffers` due to NJS deprecating the `String` byte-array fu"
},
{
"path": "docs/nginx-dns-over-https.md",
"chars": 3535,
"preview": "## DNS over HTTPS (DoH) Gateway\nUse the nginx-doh.conf file to run a DoH gateway.\nCopy the njs.d and ssl folders into /e"
},
{
"path": "examples/nginx-dns-routing.conf",
"chars": 1217,
"preview": "user nginx;\nworker_processes auto;\n\nload_module modules/ngx_stream_js_module.so;\n\nerror_log /var/log/nginx/error.log "
},
{
"path": "examples/nginx-dns-simple.conf",
"chars": 390,
"preview": "user nginx;\nworker_processes auto;\n\nerror_log /var/log/nginx/error.log notice;\npid /var/run/nginx.pid;\n\nevents"
},
{
"path": "examples/nginx-doh-and-dot-to-dns.conf",
"chars": 3447,
"preview": "user nginx;\nworker_processes auto;\n\nload_module modules/ngx_stream_js_module.so;\n\nerror_log /var/log/nginx/error.log "
},
{
"path": "examples/nginx-doh-and-dot-to-dot.conf",
"chars": 3496,
"preview": "user nginx;\nworker_processes auto;\n\nload_module modules/ngx_stream_js_module.so;\n\nerror_log /var/log/nginx/error.log "
},
{
"path": "examples/nginx-dot-to-dns-simple.conf",
"chars": 713,
"preview": "user nginx;\nworker_processes auto;\n\nload_module modules/ngx_stream_js_module.so;\n\nerror_log /var/log/nginx/error.log "
},
{
"path": "examples/nginx-dot-to-dot-routing.conf",
"chars": 1380,
"preview": "user nginx;\nworker_processes auto;\n\nload_module modules/ngx_stream_js_module.so;\n\nerror_log /var/log/nginx/error.log "
},
{
"path": "examples/nginx-plus-filtering.conf",
"chars": 3153,
"preview": "#\n# This config shows an example of filtering DNS requests using the Key/value store available in NGINX Plus\n# Push FQDN"
},
{
"path": "examples/test.conf",
"chars": 1022,
"preview": "user nginx;\nworker_processes auto;\n\nload_module modules/ngx_stream_js_module.so;\n\nerror_log /var/log/nginx/error.log "
},
{
"path": "nginx-doh.conf",
"chars": 5472,
"preview": "#\n# This config has bits of DNS/DoT/DoH all over it. See the examples folder for more targeted examples.\n#\n\nuser nginx;"
},
{
"path": "nginx-glb.conf",
"chars": 2887,
"preview": "user nginx;\nworker_processes auto;\n\nload_module modules/ngx_stream_js_module.so;\nload_module modules/ngx_stream_geoip2"
},
{
"path": "njs.d/dns/dns.js",
"chars": 8767,
"preview": "import dns from \"libdns.js\";\nexport default {get_qname, get_response, preread_doh_request, preread_dns_request, filter_d"
},
{
"path": "njs.d/dns/glb.js",
"chars": 7228,
"preview": "/**\n\n BEGIN GLB Functions\n\n**/\n\nimport dns from \"libdns.js\";\nexport default {get_response, get_edns_subnet, process_req"
},
{
"path": "njs.d/dns/libdns.js",
"chars": 19226,
"preview": "/**\n\n BEGIN DNS Functions\n\n**/\n\nexport default {dns_type, dns_class, dns_flags, dns_codes,\n parse_packet"
},
{
"path": "njs.d/dns/test.js",
"chars": 6989,
"preview": "\nimport dns from \"libdns.js\";\nexport default {get_qname, test_dns_encoder, test_dns_decoder};\n\n/**\n * DNS Decode Level\n "
},
{
"path": "ssl/certs/doh.local.pem",
"chars": 1383,
"preview": "-----BEGIN CERTIFICATE-----\nMIID0TCCArmgAwIBAgIUOdQrJG61Cs5p1PRwIOYzUClAZZkwDQYJKoZIhvcNAQEL\nBQAweDELMAkGA1UEBhMCR0IxEjA"
},
{
"path": "ssl/private/doh.local.pem",
"chars": 1704,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSnQpcd9PElrim\nhx1Absbf4SafKPpM+7NhEVBFJ5E"
}
]
About this extraction
This page contains the full source code of the TuxInvader/nginx-dns GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (75.2 KB), approximately 22.8k tokens, and a symbol index with 54 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.