Local DNS sinkhole for macOS using dnsmasq
Project description
macblock
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 statusmacblock doctor: run diagnostics and health checksmacblock logs [--component daemon|dnsmasq] [--lines N] [--follow] [--stream auto|stdout|stderr]: view logsmacblock test <domain>: test resolution against the local resolver
Control:
sudo macblock enable: enable DNS blockingsudo macblock disable: disable DNS blockingsudo 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 integrationsudo macblock uninstall [--force]: remove system integrationsudo macblock update [--source <name|url>] [--sha256 <hash>]: download + compile blocklist and reload dnsmasq
Configuration:
macblock sources list: list available blocklist sourcessudo macblock sources set <source>: set blocklist source (updates state only; runsudo macblock updateto 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 defaultsmacblock allow list: list whitelisted domainssudo macblock allow add|remove <domain>: manage whitelistmacblock deny list: list blacklisted domainssudo macblock deny add|remove <domain>: manage blacklist
Tip: macblock <command> --help shows per-command usage.
How it works
dnsmasqlistens on127.0.0.1:53and serves DNS.- The macblock daemon watches for network changes and reconciles state.
- When enabled, macblock sets DNS to
127.0.0.1for a set of managed macOS network services (it intentionally skips VPN-ish services/devices). - 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. - Blocked domains are answered as
NXDOMAINvia 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,
NXDOMAINis reported as BLOCKED. - If the domain does not match a block rule,
NXDOMAINis 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 + configurationstate.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 plistscom.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 doctorfirst. - View logs:
macblock logs --component daemon --stream stderrormacblock 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:
- StevenBlack hosts: https://github.com/StevenBlack/hosts
- HaGeZi DNS blocklists: https://github.com/hagezi/dns-blocklists
- OISD: https://oisd.nl/
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6906d574ccec85fb9f9328ef99792bdce7667da09e8b5fd117c3c8d6b75c2408
|
|
| MD5 |
e633d07cef424cef1a51e239e1b1bb6c
|
|
| BLAKE2b-256 |
34826fd827a09520037ea0be44f0c42b2c6fe367ee7ef403636893ef243e228d
|
Provenance
The following attestation bundles were made for macblock-0.2.10.tar.gz:
Publisher:
release.yml on SpyicyDev/macblock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macblock-0.2.10.tar.gz -
Subject digest:
6906d574ccec85fb9f9328ef99792bdce7667da09e8b5fd117c3c8d6b75c2408 - Sigstore transparency entry: 953779476
- Sigstore integration time:
-
Permalink:
SpyicyDev/macblock@f69c694d22391cb41a98f95a0a7344db4e0c0ef3 -
Branch / Tag:
refs/tags/v0.2.10 - Owner: https://github.com/SpyicyDev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f69c694d22391cb41a98f95a0a7344db4e0c0ef3 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
112ed2d8d01e6167b9479ec2545e1bdea6c3df6b02c5b08798156c9abfd2493a
|
|
| MD5 |
e8d6960f5ecf304ab57adaa9eb03d267
|
|
| BLAKE2b-256 |
7a2c2a0284f26fd4ddd990f965311e04387b9ff2fb9239f0a6d48d3e6701bd1d
|
Provenance
The following attestation bundles were made for macblock-0.2.10-py3-none-any.whl:
Publisher:
release.yml on SpyicyDev/macblock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
macblock-0.2.10-py3-none-any.whl -
Subject digest:
112ed2d8d01e6167b9479ec2545e1bdea6c3df6b02c5b08798156c9abfd2493a - Sigstore transparency entry: 953779506
- Sigstore integration time:
-
Permalink:
SpyicyDev/macblock@f69c694d22391cb41a98f95a0a7344db4e0c0ef3 -
Branch / Tag:
refs/tags/v0.2.10 - Owner: https://github.com/SpyicyDev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f69c694d22391cb41a98f95a0a7344db4e0c0ef3 -
Trigger Event:
push
-
Statement type: