Skip to content

chill-uk/itsalwaysdns

Repository files navigation

Docker Image Stars Python Docker

It's Always DNS (iadns)

A smarter hosts file for modern development.
Override DNS, simulate failures, and test real-world conditions, without touching host files.

It's not DNS. There's no way it's DNS. It was DNS -SSBroski

Now you can make it DNS.

Why Use It

It's Always DNS helps you test scenarios that are difficult or awkward to reproduce with static host files:

  • Mock external APIs without changing application code
  • Test mobile devices against a local backend
  • Route real domains to local services
  • Simulate slow or degraded DNS responses
  • Reproduce intermittent SERVFAIL, NXDOMAIN, or timeout behaviour
  • Apply rules to a single client IP or CIDR without affecting others
  • Update behaviour in real time via the API

30 Second Quick Start

  1. Copy .env.example to .env

  2. Update the values in .env as needed

  3. Start the application:

    • Using Docker:

      docker compose up
    • Or run locally:

      python3 app/main.py
  4. Create a rule:

    curl -X POST http://[HostIP/localhost]:8080/rules \
      -H 'Content-Type: application/json' \
      -d '{"domain":"api.example.com","rule":{"default":{"config":{"ttl":10,"ip":"127.0.0.1"},"enabled":true}}}'
  5. Test DNS:

    dig @127.0.0.1 -p 53 api.example.com

    If the response is 127.0.0.1, it's working.

  6. Update your hosts or servers to use iadns as the primary DNS server, then start testing immediately.

Examples

1. Break GitHub (for one device only)

{
  "github.com": {
    "clients": {
      "192.168.1.50": {
        "config": {
          "failure_rate": 1.0,
          "failure_response": "nxdomain"
        }
      }
    }
  }
}

2. Mock external APIs (no code changes)

{
  "api.stripe.com": {
    "default": {
      "enabled": true,
      "config": {
        "ip": "192.168.1.100",
        "ttl": 10
      }
    }
  }
}

Rules Workflow

rule-workflow

Rule Model

Rules are grouped by domain. Each domain can have a default rule and optional per-client overrides:

{
  "api.example.com": {
    "default": {
      "enabled": true,
      "config": {
        "delay_ms": 500,
        "jitter_ms": 100,
        "ttl": 10
      }
    },
    "clients": {
      "192.168.1.50": {
        "enabled": true,
        "config": {
          "ip": "192.168.1.50",
          "failure_rate": 0.3,
          "failure_response": "servfail",
          "ttl": 10
        }
      }
    }
  }
}

Client rules can use exact IPv4 addresses or CIDR ranges. Wildcard domains such as *.example.com are supported.

Matching prefers specificity:

  1. Exact client IP on exact domain
  2. Client CIDR on exact domain
  3. Exact client IP on wildcard domain
  4. Client CIDR on wildcard domain
  5. Default on exact domain
  6. Default on wildcard domain
  7. Upstream DNS fallback

Config effects run in this order:

  1. Failure chance
  2. Delay and jitter
  3. IP override or upstream resolution
  4. TTL rewrite

Persistence

Rules are stored in memory by default.

To save rules to local JSON, set RULES_FILE.

Python

RULES_FILE=./rules.json python3 app/main.py

Docker

Uncomment the volume configuration in docker-compose.yml:

    volumes:
      - itsalwaysdns-data:/data

volumes:
  itsalwaysdns-data:

Also in your .env file:

RULES_FILE=/data/rules.json

Configuration

Environment variables:

  • DNS_PORT, default 53
  • HTTP_PORT, default 8080
  • UPSTREAM_DNS, optional override
  • UPSTREAM_PORT, default 53
  • DEFAULT_TTL, default 10
  • RULES_FILE, optional path to a JSON rules file

When UPSTREAM_DNS is unset, iadns reads the first nameserver from /etc/resolv.conf. In Docker, that normally means it uses Docker's injected resolver, so container DNS behavior stays aligned with the host/container environment. If no system resolver can be detected, it falls back to 1.1.1.1.

Using port 53 is convenient when you want the app to act like a normal DNS server, but some local environments require elevated privileges for ports below 1024. If you want a no-privilege local setup, use a high port:

DNS_PORT=8053 python3 app/main.py
dig @127.0.0.1 -p 8053 api.example.com

Docker health support

curl http://[HostIP/localhost]:8080/health

Testing

The test suite uses the Python standard library:

python3 -m unittest discover

API docs

Current DNS Scope

IP overrides are intentionally IPv4 A-record focused. Other query types, including AAAA, are forwarded upstream unless a failure or delay rule applies.

That keeps the tool small and predictable for local development. A future version could add explicit AAAA/IPv6 overrides if that becomes important.

About

A lightweight API DNS server for development that enables domain overrides, per-client rules, and simulation of latency, failures, and flaky responses.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors