DNS proxy running on NodeJS. It acts as DNS server running locally, that returns specified fake responses for given particular host names, and passes real responses from upstream DNS server for others transparently byte-to-byte (so, these passed responses can be of any type, not necesarily supported by the application). Currently, the application supports forging responses of type "A" (IPv4 address) and "CNAME" (canonical name).
Forging of IPv6 responses is not supported. If request of type "AAAA" (IPv6) is received and hostname appears in list of hostnames to forge responses for, then "Not implemented" response is returned to sender. This is done for cases when local clients use both IPv4 and IPv6 protocols to resolve host IP. Otherwise, they could be able to fetch real IPv6 address, while getting fake IPv4 one.
To start the application, open console in application root directory and run the command:
node index
On PC whose DNS requests should be proxied, set the IP address of PC running DNS Proxy. If this is one and the same PC, set 127.0.0.1
.
Configuration is stored in config.json file in root directory of the project. Configuration parameters are defined as properties of that JSON file:
{
"remoteDnsConnectionMode": "udp",
"upstreamDnsUdpHost": "8.8.8.8",
"upstreamDnsUdpPort": 53,
"localDnsPort": 53,
"forgedRequestsTTL": 10,
"upstreamDnsTlsHost": "8.8.8.8",
"upstreamDnsTlsPort": 853,
"requestsToForge": []
}
Please mention: By default, the application has example config.json file with default settings listed above.
The file may be edited and saved while the proxy is running, the updated configuration will be fetched on the fly in this case, with no need to restart the application. The only exclusions are:
- switching connection type from 'udp' to 'tls',
- changing local connection parameters,
- changing TLS connection parameters.
These changes will require restarting of the application.
As configuration is stored in JSON file, coments are not supported. As a workaround, one may add some arbitrarily named field, and fill its value with comment text, like:
"comment_1": "this is comment"
These parameters define how the application will connect to upstream (up-level) DNS server:
remoteDnsConnectionMode
[udp
ortls
]: connection protocol for upstream DNS server. UDP and TLS are currently supported. Default:udp
.upstreamDnsUdpHost
: IP address of upstream DNS server for UDP protocol (only one address is supported for now). Default:8.8.8.8
(Google DNS).upstreamDnsUdpPort
: port to connect on upstream DNS server with UDP protocol. Default:53
.upstreamDnsTlsHost
: IP address of upstream DNS server for TLS protocol (only one address is supported for now). Default:8.8.8.8
(Google DNS).upstreamDnsTlsPort
: port to connect on upstream DNS server with TLS protocol. Default853
.
upstreamDnsUdpHost
and upstreamDnsUdpPort
are required and applied only if remoteDnsConnectionMode
is udp
. And vise versa, upstreamDnsTlsHost
and upstreamDnsTlsPort
are required and applied only if remoteDnsConnectionMode
is tls
.
These parameters specify, how the application will accept incoming local connections from clients:
localDnsPort
: local port for incoming connections. Default:53
(standard DNS port).forgedRequestsTTL
: TTL (time to live) for forged responses, in seconds. Default:10
.
Incoming local requests are only accepted over UDP protocol.
Please mention: application accepts network connections on all available network interfaces. Whether you need to limit it, please concider using firewall.
Hostnames and related responses are specified in requestsToForge
section of the config. This section is in form of JSON array, where each target hostname is specified in separate array item, along with given response. The hostname to forge response for should be specified in hostName
field of that item. Other fields of the item define parameters of the response that will be returned when that particulad hostname is requested.
"requestsToForge": [
{
"comment": "Serve requests to 'example.com' locally, returning ip address 127.0.0.1",
"hostName": "example.com",
"ip": "127.0.0.1"
},
{
"comment": "Serve requests to 'prod.example.com' like it is an alias for canonical name 'dev.example.com', i.e. CNAME behavior",
"hostName": "prod.example.com",
"cname": "dev.example.com"
},
{
"comment": "Forward requests for subdomains of 'example.com' to local network ip 192.168.0.15",
"hostName": "*.example.com",
"ip": "192.168.0.15"
},
{
"comment": "Serve requests to 'another-example.com' locally",
"hostName": "another-example.com",
"ip": "127.0.0.1"
}
]
Please mention: four entrances mentioned above will present in default config as example, feel free to remove them before using the application.
Changes in this section are applied on the fly, without restarting the application.
Currently, the application supports mocking responses of type "A" (IPv4 address) and "CNAME" (canonical name).
Request hostnames may be specified literally (i.e. example.com
) or using wildcard templates (like *.example.com
, *example*
, example.*.com
or so). *
symbol means that group of any symbols may appear in its place, including none symbols at all. Concider the following examples:
- Pattern
example.com
will match hostnameexample.com
and only it. - Pattern
*example.com
will match hostnames of a kindexample.com
andwww.example.com
. - Pattern
*.example.com
will matchwww.example.com
andwww.test.example.com
, but NOTexample.com
. - Pattern
*example*
will matchwww.example.com
,example.net
andtest-example.org.net
. - Pattern
test*.example.com
will matchtest.example.com
,test1.example.com
, andtest.test.test.example.com
.
requestsToForge
's items are iterated one by one, till the first match occurs. Say, if you have records with patterns *.example.com
and www.example.com
(in that order) in your config, and hostname www.example.com
is requested, it will match first pattern *.example.com
and stop on it, not reaching www.example.com
record. So, the more specific records should be placed above the less specific ones in the config.
If you need to return specific IP address for given hostname, create a record in requestsToForge
section, contaning the hostname in hostName
field, and IP address in ip
field respectively, like that:
{
"comment": "Serve requests to 'example.com' locally",
"hostName": "example.com",
"ip": "127.0.0.1"
}
Edit it according to your needs, specifying proper hostName
and ip
.
Currently, only one IP can be set for specific hostname.
As mentioned above, if request of type "AAAA" (IPv6) matches the hostname, then "Not implemented" response is returned. So, if some local client sends two requests: one to resolve IPv4 address, and another for IPv6, then the application sends fake response for IPv4, and "Not implemented" for IPv6. Otherwise, client could bypass proxy and get real IPv6 address.
Instead of fake IP, there is an ability to return response of CNAME type.
In other words, application can return specific canonical name for given requested hostname, as if it was an alias for the returned hostname. To achieve that, add a record contaning hostName
field with the target hostname (that we'll pretend is an alias), and cname
field with fake canonical hostname, to requestsToForge
section. Like the following:
{
"comment": "Serve requests to 'prod.example.com' like it is an alias for canonical name 'dev.example.com'",
"hostName": "prod.example.com",
"cname": "dev.example.com"
}
If incoming request itself is of type CNAME, then forged response will contain fake canonical name only. If request is of type A (IPv4 address), then response, in addition to canonical name, will contain IP address(es) for it, resolving them with up-level DNS server. See explanation in RFC-1034, Section 5.2.2.
Tests are very basic, assuming that if running tests produce some output in console without throwing an error, they are concidered to pass. To run tests, in application root directory, run in console:
node tests/test.js
This project is licensed under the terms of the MIT license (see the LICENSE file in root directory of the project).