Skip to main content

dns-rewrite-proxy CircleCI Test Coverage

A DNS proxy server that conditionally rewrites and filters A record requests. Written in Python, all code is in a single module, and there is a single dependency, aiodnsresolver.

CNAMEs are followed and resolved by the proxy to IP addresses, and never returned to the client.

Installation

pip install dnsrewriteproxy

Usage

By default the proxy will listen on port 53, and proxy requests to the servers in /etc/resolv.conf. However, by default all requests are blocked without explicit rules, so to proxy requests you must configure at least one rewrite rule.

from dnsrewriteproxy import DnsProxy

# Proxy all incoming A record requests without any rewriting
start = DnsProxy(rules=((r'(^.*$)', r'\1'),))

# Run proxy, accepting UDP requests on port 53
await start()

The rules parameter must be an iterable [e.g. a list or a tuple] of tuples, where each tuple is regex pattern/replacement pair, passed to re.subn under the hood. On each incoming DNS request from downstream for a domain

  • this list is iterated over;
  • the first rule that matches the incoming domain name is used to rewrite the domain, the upstream DNS server is queried for A records, and these records, or error code, is returned downstream;
  • and if no rule matches a REFUSED response is returned downstream.

The response of REFUSED is deliberate for clients to be able to help differentiate between a configuration issue on the proxy, the proxy not working or not being contactable, and a domain actually not existing.

So to rewrite all queries for www.source.com to www.target.com, and to refuse to proxy any others, you can use the following configuration.

start = DnsProxy(rules=(
    (r'^www\.source\.com$', r'www.target.com'),
))

Alternatively, do the same rewriting, but to allow all other requests, you can use the following.

start = DnsProxy(rules=(
    (r'^www\.source\.com$', r'www.target.com'),
    (r'(^.*$)', r'\1'),
))

To proxy to a server other than that specified in /etc/resolv.conf, you can pass a customised Resolver via get_resolver.

from aiodnsresolver import Resolver
from dnsrewriteproxy import DnsProxy

def get_resolver():
    async def get_nameservers(_, __):
        for _ in range(0, 5):
            yield (0.5, ('8.8.8.8', 53))
    return Resolver(get_nameserver=get_nameservers)

start = DnsProxy(
    rules=((r'(^.*$)', r'\1'),),
    get_resolver=get_resolver,
)

Server lifecycle

In the above example await start() completes just after the server has started listening. The coroutine start returns the underlying task to give control over the server lifecycle. A task can be seen as an "asyncio thread"; this is exposed to allow the server to sit in a larger asyncio Python program that may have a specific startup/shutdown procedure.

Run forever

You can run the server forever [or until it hits some non-recoverable error] by awaiting this task.

from dnsrewriteproxy import DnsProxy

start = DnsProxy(rules=((r'(^.*$)', r'\1'),))
server_task = await start()

# Waiting here until the server is stopped
await server_task

Stopping the server

To stop the server, you can cancel the returned task.

from dnsrewriteproxy import DnsProxy

start = DnsProxy(rules=((r'(^.*$)', r'\1'),))
proxy_task = await start()

# ... Receive requests

# Initiate stopping: new requests will not be processed...
proxy_task.cancel()

try:
    # ... and we wait until previously received requests have been processed
    await proxy_task
except asyncio.CancelledError:
    pass

Graceful shutdown example

A full example of a server that would do a graceful shutdown on SIGINT or SIGTERM is below.

import asyncio
import signal

from dnsrewriteproxy import (
    DnsProxy,
)

async def async_main():
    start = DnsProxy(rules=((r'(^.*$)', r'\1'),))
    proxy_task = await start()

    loop = asyncio.get_running_loop()
    loop.add_signal_handler(signal.SIGINT, proxy_task.cancel)
    loop.add_signal_handler(signal.SIGTERM, proxy_task.cancel)

    try:
        await proxy_task
    except asyncio.CancelledError:
        pass

asyncio.run(async_main())
print('End of program')

Metadata

Release files for dnsrewriteproxy 0.0.16

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dnsrewriteproxy 0.0.16
File Size Uploaded Provenance
dnsrewriteproxy-0.0.16.tar.gz 5.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for dnsrewriteproxy 0.0.16
File Interpreter ABI Platform
dnsrewriteproxy-0.0.16-py3-none-any.whl Python 3 none any Details

Total release size: 12.1 kB

Release files / dnsrewriteproxy-0.0.16.tar.gz

Download URL dnsrewriteproxy-0.0.16.tar.gz
Size 5.6 kB
Tags Source
SHA-256 checksum
How to use checksums
3af972f59d6170d3c0aaa61a41cf7b057c06ca13c7907cfa4b9f609b62c7622d
BLAKE2b-256 checksum
How to use checksums
cb8aef873ba8f1ab5cfc02c741ceb9cd95b5fd8ec96638f66f00a8f39ab9621e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.12.1 pkginfo/1.4.2 requests/2.22.0 setuptools/41.4.0 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.1

Release files / dnsrewriteproxy-0.0.16-py3-none-any.whl

Download URL dnsrewriteproxy-0.0.16-py3-none-any.whl
Size 6.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
82c11126bc2031df475cb4d891ca91517990defc2a75f23ab20024d5537e5443
BLAKE2b-256 checksum
How to use checksums
cfdfb88063af67239386dd9e9b7cfa6580a4332f007b82c92a49abb266559fcb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.12.1 pkginfo/1.4.2 requests/2.22.0 setuptools/41.4.0 requests-toolbelt/0.8.0 tqdm/4.28.1 CPython/3.7.1

Security for dnsrewriteproxy 0.0.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page