Skip to main content

Local DNS sinkhole for macOS using dnsmasq

Project description

macblock

CI PyPI License: MIT

Local DNS sinkhole for macOS using dnsmasq on 127.0.0.1:53, with automatic system DNS configuration and split-DNS preservation.

Why?

I like Pi-Hole, but I don't necessarily want to deploy a whole box on a network for DNS adblock, or have the whole network go through it. I also wanted a better solution than running Pi-Hole in a container on MacOS, which felt like adding a whole lot of overhead for a relatively lightweight service. After some searching, I found I could configure dnsmasq myself and run a fairly simple sinkhole as a service. macblock is a CLI and long-running management service that make the installation, configuration, and management of a dnsmasq-based DNS sinkhole on MacOS easy.

Vibe-coding disclaimer

Most of this project was vibe-coded with careful code review and analysis of what the daemon touches. Every effort was made to ensure that the services here are using root as little as possible. If you find any holes I missed, please open an issue or a PR!

Features

  • Blocks ads, trackers, and malware at the DNS level
  • Automatically configures DNS for managed network services
  • Preserves VPN/corporate split-DNS routing
  • Pause/resume with automatic timers
  • Whitelist/blacklist management
  • Multiple blocklist sources (StevenBlack, HaGeZi, OISD) or a custom URL

Install

Via Homebrew (recommended)

brew install SpyicyDev/formulae/macblock

Or, if you prefer adding the tap explicitly:

brew tap SpyicyDev/formulae
brew install macblock

Via PyPI

This installs the macblock CLI, but you still need dnsmasq available on your system (recommended via Homebrew):

brew install dnsmasq
python3 -m pip install macblock

Quick start

macblock performs privileged operations (launchd + system DNS changes). You can run root-required commands with sudo, or omit it and let macblock auto-elevate.

sudo macblock install
sudo macblock enable
macblock status

Usage

macblock <command> [flags]

Global flags

  • -h, --help: show help for a command
  • -V, --version: show version

Commands

Status & diagnostics:

  • macblock status: show current status
  • macblock doctor: run diagnostics and health checks
  • macblock logs [--component daemon|dnsmasq] [--lines N] [--follow] [--stream auto|stdout|stderr]: view logs
  • macblock test <domain>: test resolution against the local resolver

Control:

  • sudo macblock enable: enable DNS blocking
  • sudo macblock disable: disable DNS blocking
  • sudo macblock pause <duration>: temporarily disable (e.g. 10m, 2h, 1d)
  • sudo macblock resume: resume blocking

Installation & updates:

  • sudo macblock install [--force] [--skip-update]: install system integration
  • sudo macblock uninstall [--force]: remove system integration
  • sudo macblock update [--source <name|url>] [--sha256 <hash>]: download + compile blocklist and reload dnsmasq

Configuration:

  • macblock sources list: list available blocklist sources
  • sudo macblock sources set <source>: set blocklist source (updates state only; run sudo macblock update to download/compile/apply)
  • macblock upstreams list: list configured fallback upstream DNS servers (if any)
  • sudo macblock upstreams set [ip ...]: set fallback upstream DNS servers (prompts if none provided)
  • sudo macblock upstreams reset: reset fallback upstream DNS servers to built-in defaults
  • macblock allow list: list whitelisted domains
  • sudo macblock allow add|remove <domain>: manage whitelist
  • macblock deny list: list blacklisted domains
  • sudo macblock deny add|remove <domain>: manage blacklist

Tip: macblock <command> --help shows per-command usage.

How it works

  1. dnsmasq listens on 127.0.0.1:53 and serves DNS.
  2. The macblock daemon watches for network changes and reconciles state.
  3. When enabled, macblock sets DNS to 127.0.0.1 for a set of managed macOS network services (it intentionally skips VPN-ish services/devices).
  4. macblock generates dnsmasq upstream routing by preferring DHCP nameservers from the default-route interface, and falling back to system resolvers (scutil --dns), so domain-specific resolvers (VPN/corporate split-DNS) keep working.
  5. Blocked domains are answered as NXDOMAIN via dnsmasq rules.
flowchart TD
  U[User] -->|macblock CLI| CLI[macblock]
  CLI -->|writes/updates| ST[(state.json)]

  LD[launchd] --> D[macblock daemon]
  D -->|reads/writes| ST
  D -->|sets per-service DNS to 127.0.0.1| SCD[System DNS config]
  D -->|generates upstream.conf from DHCP/scutil| UP[(upstream.conf)]

  SCD --> DNS[dnsmasq on 127.0.0.1:53]
  UP --> DNS
  DNS -->|forwards allowed queries| R[Upstream resolvers]

  NC[Network change events] --> D

Note: Encrypted DNS (DoH/DoT) can bypass macblock; macblock doctor warns if detected.

macblock test behavior

macblock test <domain> runs dig against 127.0.0.1:53 and uses the compiled block rules to interpret NXDOMAIN:

  • If the domain matches a block rule, NXDOMAIN is reported as BLOCKED.
  • If the domain does not match a block rule, NXDOMAIN is reported as does not exist.

If you don't have dig, install it with:

brew install bind

Uninstall

See docs/UNINSTALL.md.

Quick version:

sudo macblock uninstall

# If installed via Homebrew
brew uninstall macblock dnsmasq

# If installed via PyPI
python3 -m pip uninstall macblock

Filesystem footprint

macblock keeps its on-disk footprint intentionally small and predictable:

  • /Library/Application Support/macblock/: persistent state + configuration
    • state.json, version, blocklist.*, whitelist.txt, blacklist.txt, dns.exclude_services, upstream.fallbacks, etc/dnsmasq.conf
  • /Library/Logs/macblock/: service logs (managed by launchd)
    • daemon.{out,err}.log, dnsmasq.{out,err}.log
  • /var/db/macblock/: runtime state (safe to delete; will be recreated)
    • upstream.conf, upstream.info.json, daemon.pid, daemon.ready, daemon.last_apply, dnsmasq/dnsmasq.pid
  • /Library/LaunchDaemons/: launchd plists
    • com.local.macblock.daemon.plist, com.local.macblock.dnsmasq.plist

macblock also modifies system DNS settings via networksetup.

Service classification and overrides

macblock chooses a set of “managed” network services to apply DNS changes to (and intentionally skips VPN-ish services/devices) using a heuristic.

If a service is being managed when you don't want it to be (or vice-versa), you can override selection by creating:

  • /Library/Application Support/macblock/dns.exclude_services

Format:

  • One exact service name per line.
  • Blank lines are ignored.
  • Lines starting with # are comments.

To find the exact service names on your system:

/usr/sbin/networksetup -listallnetworkservices

Example:

# Keep macblock from touching these services
Tailscale Tunnel
My VPN

Troubleshooting

  • Run macblock doctor first.
  • View logs: macblock logs --component daemon --stream stderr or macblock logs --component dnsmasq --stream stderr.
  • Verify DNS state: scutil --dns (macblock uses this to preserve split DNS).
  • Port conflict on :53: sudo lsof -i :53 -P -n.

Attribution

macblock can download third-party blocklists from:

Please review each upstream project's license/terms. macblock does not vendor or redistribute these lists in this repository.

Security

See SECURITY.md for the threat model and privileged footprint.

Development

Tooling:

brew install just direnv

Then:

direnv allow
just sync
just ci

License

MIT

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

macblock-0.2.10.tar.gz (59.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

macblock-0.2.10-py3-none-any.whl (53.0 kB view details)

Uploaded Python 3

File details

Details for the file macblock-0.2.10.tar.gz.

File metadata

  • Download URL: macblock-0.2.10.tar.gz
  • Upload date:
  • Size: 59.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for macblock-0.2.10.tar.gz
Algorithm Hash digest
SHA256 6906d574ccec85fb9f9328ef99792bdce7667da09e8b5fd117c3c8d6b75c2408
MD5 e633d07cef424cef1a51e239e1b1bb6c
BLAKE2b-256 34826fd827a09520037ea0be44f0c42b2c6fe367ee7ef403636893ef243e228d

See more details on using hashes here.

Provenance

The following attestation bundles were made for macblock-0.2.10.tar.gz:

Publisher: release.yml on SpyicyDev/macblock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macblock-0.2.10-py3-none-any.whl.

File metadata

  • Download URL: macblock-0.2.10-py3-none-any.whl
  • Upload date:
  • Size: 53.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for macblock-0.2.10-py3-none-any.whl
Algorithm Hash digest
SHA256 112ed2d8d01e6167b9479ec2545e1bdea6c3df6b02c5b08798156c9abfd2493a
MD5 e8d6960f5ecf304ab57adaa9eb03d267
BLAKE2b-256 7a2c2a0284f26fd4ddd990f965311e04387b9ff2fb9239f0a6d48d3e6701bd1d

See more details on using hashes here.

Provenance

The following attestation bundles were made for macblock-0.2.10-py3-none-any.whl:

Publisher: release.yml on SpyicyDev/macblock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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