Monitor JSON lease files and sync them to Unbound DNS server
Project description
json-leases-to-unbound
A Python service that monitors JSON lease files and automatically synchronizes them to an Unbound DNS server in real-time. This tool bridges the gap between DHCP/SLAAC lease management systems and local DNS resolution.
Features
- ๐ Real-time Monitoring - Watches lease directories for changes using filesystem events
- ๐ IPv4 & IPv6 Support - Handles both A and AAAA DNS records with automatic PTR record generation
- ๐ฏ Smart Lease Management - Automatically adds, updates, and removes DNS entries based on lease state
- ๐ Flexible Unbound Integration - Supports local and remote Unbound servers with custom config files
- ๐ Comprehensive Logging - Configurable log levels for debugging and monitoring
- โ
Well Tested - 91% code coverage with 33 passing tests
- Coverage badge above powered by Codecov (uploaded from GitHub Actions)
How It Works
- Monitors a directory containing JSON lease files (typically from SLAAC or DHCP services)
- Extracts hostname and IP address information from lease files
- Generates DNS records (AAAA/A and PTR) for each active lease
- Synchronizes with Unbound DNS server using
unbound-control - Updates DNS records when leases change or expire
DNS Record Generation
For each lease, the service creates:
- AAAA record (or A for IPv4): Maps hostname to IP address
- Example:
test-host.lan IN AAAA 2001:db8::1
- Example:
- PTR record: Maps IP address back to hostname
- Example:
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa PTR test-host.lan
- Example:
Installation
From Source
git clone https://github.com/junior/json-leases-to-unbound.git
cd json-leases-to-unbound
pip install -e .
Using pip (when published)
pip install json-leases-to-unbound
Usage
Command Line
json-leases-to-unbound [OPTIONS]
Options
| Option | Default | Description |
|---|---|---|
--log-level |
INFO |
Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL |
--source |
/run/slaac-resolver/ |
Source directory containing JSON lease files |
--domain |
lan |
Default domain suffix for hostnames |
--unbound-server |
None |
Remote Unbound server (IP:port) |
--config-file |
None |
Path to Unbound config file |
Examples
Monitor default directory with INFO logging:
json-leases-to-unbound
Monitor custom directory with debug logging:
json-leases-to-unbound --source /var/lib/dhcp/leases --log-level DEBUG
Use custom domain:
json-leases-to-unbound --domain home.local
Connect to remote Unbound server:
json-leases-to-unbound --unbound-server 192.168.1.1:8953
Use custom Unbound config:
json-leases-to-unbound --config-file /etc/unbound/unbound.conf
As a Python Module
from json_leases_to_unbound import main
main(
log_level='INFO',
source='/run/slaac-resolver/',
domain='lan',
unbound_server=None,
config_file=None
)
JSON Lease File Format
The service expects JSON files with the following structure:
[
{
"Address": [8193, 3512, 0, 0, 0, 0, 0, 1],
"AddressType": "IPv6",
"Hostname": "test-host",
"Expire": "2025-12-31T23:59:59Z"
},
{
"Address": [192, 168, 1, 100],
"AddressType": "IPv4",
"Hostname": "another-host",
"Expire": "2025-12-31T23:59:59Z"
}
]
Field Descriptions
- Address: Array of integers representing the IP address
- IPv6: 8 elements (e.g.,
[8193, 3512, 0, 0, 0, 0, 0, 1]โ2001:db8::1) - IPv4: 4 elements (e.g.,
[192, 168, 1, 100]โ192.168.1.100)
- IPv6: 8 elements (e.g.,
- AddressType: String indicating address type (
"IPv4"or"IPv6") - Hostname: String hostname for DNS entry
- Expire: ISO 8601 timestamp for lease expiration
Requirements
System Requirements
- Python 3.8 or higher
unbound-controlbinary (part of Unbound DNS server)- Appropriate permissions to execute
unbound-control
Python Dependencies
watchdog>=2.1.9,<3.0- Filesystem monitoring
Development/Testing Dependencies
pytest>=7.0.0pytest-mock>=3.10.0pytest-cov>=4.0.0
Development
Setting Up Development Environment
# Clone the repository
git clone https://github.com/junior/json-leases-to-unbound.git
cd json-leases-to-unbound
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install in editable mode with test dependencies
pip install -e ".[test]"
Running Tests
# Run all tests
pytest
# Run with coverage report
pytest --cov=json_leases_to_unbound --cov-report=html
# Run specific test file
pytest tests/test_core.py
# Run with verbose output
pytest -v
See TESTING.md for detailed testing documentation.
Project Structure
json-leases-to-unbound/
โโโ json_leases_to_unbound/
โ โโโ __init__.py # Package initialization
โ โโโ __main__.py # Entry point for python -m
โ โโโ cli.py # Command-line interface
โ โโโ core.py # Core functionality
โโโ tests/
โ โโโ __init__.py
โ โโโ conftest.py # Pytest fixtures
โ โโโ test_cli.py # CLI tests
โ โโโ test_core.py # Core functionality tests
โ โโโ README.md # Testing documentation
โโโ pyproject.toml # Project metadata and build config
โโโ requirements.txt # Dependencies
โโโ README.md # This file
โโโ TESTING.md # Testing guide
How It Works Internally
Monitoring Process
- Initial Scan: Processes all existing lease files in the source directory
- Event Loop: Watches for filesystem events (create, modify, delete)
- Change Detection: Compares new lease data with active leases in memory
- DNS Updates: Generates and applies appropriate
unbound-controlcommands
Lease State Management
The service maintains an in-memory cache of active leases keyed by filename and hostname. When changes are detected:
- New Lease: Adds DNS records to Unbound
- Modified Lease: Removes old records and adds new ones
- Deleted File: Removes all DNS records associated with that file
- Expired/Missing Lease: Removes DNS records for leases no longer in file
Unbound Control Integration
The service discovers the unbound-control binary automatically in common locations:
/usr/sbin/unbound-control/usr/bin/unbound-control/usr/local/sbin/unbound-control- Any location in
PATH
Commands used:
unbound-control local_datas- Add DNS recordsunbound-control local_datas_remove- Remove DNS records
Use Cases
SLAAC (Stateless Address Autoconfiguration)
Monitor IPv6 SLAAC leases and provide DNS resolution for auto-configured addresses:
json-leases-to-unbound --source /run/slaac-resolver/ --domain lan
DHCPv4/DHCPv6 Integration
Integrate with DHCP servers that export lease information as JSON:
json-leases-to-unbound --source /var/lib/dhcp/leases/ --domain home.local
Multi-Site DNS Management
Connect to remote Unbound servers for centralized DNS management:
json-leases-to-unbound \
--source /var/lib/leases/ \
--unbound-server 10.0.0.1:8953 \
--domain site1.internal
Troubleshooting
Service Not Finding unbound-control
Ensure unbound-control is installed and in PATH:
which unbound-control
If not found, install Unbound:
# Debian/Ubuntu
sudo apt-get install unbound
# Fedora/RHEL
sudo dnf install unbound
# macOS
brew install unbound
Permission Denied Errors
Ensure the user running the service has permissions to:
- Read the source lease directory
- Execute
unbound-control - Access Unbound's control socket (typically
/var/run/unbound/unbound.ctl)
You may need to add the user to the appropriate group:
sudo usermod -a -G unbound your-username
Lease Files Not Being Processed
Check log output with DEBUG level:
json-leases-to-unbound --log-level DEBUG --source /your/path
Verify JSON file format matches the expected structure.
DNS Records Not Appearing
Test unbound-control manually:
# Add a test record
echo "test.lan IN A 192.168.1.100" | sudo unbound-control local_datas
# Query the record
dig @localhost test.lan
# Remove the record
echo "test.lan IN A 192.168.1.100" | sudo unbound-control local_datas_remove
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Workflow
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests (
pytest) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Code Style
- Follow PEP 8 guidelines
- Add tests for new functionality
- Update documentation as needed
- Maintain or improve code coverage
License
MIT License - see LICENSE file for details
Author
Junior - cjuniorfox@gmail.com
Links
- Repository: https://github.com/junior/json-leases-to-unbound
- Issues: https://github.com/junior/json-leases-to-unbound/issues
- Testing Guide: TESTING.md
- Test Documentation: tests/README.md
Acknowledgments
- Built with watchdog for filesystem monitoring
- Integrates with Unbound DNS
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 json_leases_to_unbound-0.0.2.tar.gz.
File metadata
- Download URL: json_leases_to_unbound-0.0.2.tar.gz
- Upload date:
- Size: 16.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b77946937cbcc4e106cc5c100ce0220960a43577ed5b2b510c393efc205dd4d6
|
|
| MD5 |
956ad9f08c9cbe0245aa5e8b79bf77e2
|
|
| BLAKE2b-256 |
f8a53de1675a4db44c690fdf97e1fd1d4e9d81bea32b88df1c833d28035795ca
|
Provenance
The following attestation bundles were made for json_leases_to_unbound-0.0.2.tar.gz:
Publisher:
publish.yml on cjuniorfox/json-leases-to-unbound
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
json_leases_to_unbound-0.0.2.tar.gz -
Subject digest:
b77946937cbcc4e106cc5c100ce0220960a43577ed5b2b510c393efc205dd4d6 - Sigstore transparency entry: 708066389
- Sigstore integration time:
-
Permalink:
cjuniorfox/json-leases-to-unbound@32dbe5f8892c061a8d57d60e21132757f78974c5 -
Branch / Tag:
refs/tags/v0.0.2 - Owner: https://github.com/cjuniorfox
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32dbe5f8892c061a8d57d60e21132757f78974c5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file json_leases_to_unbound-0.0.2-py3-none-any.whl.
File metadata
- Download URL: json_leases_to_unbound-0.0.2-py3-none-any.whl
- Upload date:
- Size: 9.9 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 |
983c20f1af6894c50451fd5b4cd9a84b294c5e51883b9af2e4a6a1581e16cf98
|
|
| MD5 |
df76c96729b6c452d98b4a060185e936
|
|
| BLAKE2b-256 |
926d3452f89b64aae64d2f4a99623e8c285a7f8a67540048f14303c03a6b899b
|
Provenance
The following attestation bundles were made for json_leases_to_unbound-0.0.2-py3-none-any.whl:
Publisher:
publish.yml on cjuniorfox/json-leases-to-unbound
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
json_leases_to_unbound-0.0.2-py3-none-any.whl -
Subject digest:
983c20f1af6894c50451fd5b4cd9a84b294c5e51883b9af2e4a6a1581e16cf98 - Sigstore transparency entry: 708066394
- Sigstore integration time:
-
Permalink:
cjuniorfox/json-leases-to-unbound@32dbe5f8892c061a8d57d60e21132757f78974c5 -
Branch / Tag:
refs/tags/v0.0.2 - Owner: https://github.com/cjuniorfox
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32dbe5f8892c061a8d57d60e21132757f78974c5 -
Trigger Event:
release
-
Statement type: