Skip to main content

A DNS proxy server that conditionally rewrites and filters A record requests

Project description

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.

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 queries 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')

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dnsrewriteproxy-0.0.11.tar.gz (5.5 kB view details)

Uploaded Source

Built Distribution

dnsrewriteproxy-0.0.11-py3-none-any.whl (6.5 kB view details)

Uploaded Python 3

File details

Details for the file dnsrewriteproxy-0.0.11.tar.gz.

File metadata

  • Download URL: dnsrewriteproxy-0.0.11.tar.gz
  • Upload date:
  • Size: 5.5 kB
  • Tags: Source
  • Uploaded using 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

File hashes

Hashes for dnsrewriteproxy-0.0.11.tar.gz
Algorithm Hash digest
SHA256 a71f41fbe475b3106912729b67f9e6fa08821258b07e718c799a48aa5d94b936
MD5 b69c3c526f6c1a8d94098c63a75f9230
BLAKE2b-256 fd4a00a5be24d3bc57033cae36b33769b068ffde556e0a67b08757550a97c7d1

See more details on using hashes here.

File details

Details for the file dnsrewriteproxy-0.0.11-py3-none-any.whl.

File metadata

  • Download URL: dnsrewriteproxy-0.0.11-py3-none-any.whl
  • Upload date:
  • Size: 6.5 kB
  • Tags: Python 3
  • Uploaded using 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

File hashes

Hashes for dnsrewriteproxy-0.0.11-py3-none-any.whl
Algorithm Hash digest
SHA256 f340e6724319c969b46ca8cac414e53e4a1f90f4c2949ce8080946ee864ba9ea
MD5 0a56ea2fff16cabc92819526912543c1
BLAKE2b-256 83238eac196c4ff9e7f3c626b8cf16a25616254c42b231271d0215fda88c8fe1

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page