Skip to main content

Monitor JSON lease files and sync them to Unbound DNS server

Project description

json-leases-to-unbound

CI codecov

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

  1. Monitors a directory containing JSON lease files (typically from SLAAC or DHCP services)
  2. Extracts hostname and IP address information from lease files
  3. Generates DNS records (AAAA/A and PTR) for each active lease
  4. Synchronizes with Unbound DNS server using unbound-control
  5. 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
  • 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

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)
  • 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-control binary (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.0
  • pytest-mock>=3.10.0
  • pytest-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

  1. Initial Scan: Processes all existing lease files in the source directory
  2. Event Loop: Watches for filesystem events (create, modify, delete)
  3. Change Detection: Compares new lease data with active leases in memory
  4. DNS Updates: Generates and applies appropriate unbound-control commands

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 records
  • unbound-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

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Run tests (pytest)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. 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

Acknowledgments

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

json_leases_to_unbound-0.0.2.tar.gz (16.0 kB view details)

Uploaded Source

Built Distribution

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

json_leases_to_unbound-0.0.2-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

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

Hashes for json_leases_to_unbound-0.0.2.tar.gz
Algorithm Hash digest
SHA256 b77946937cbcc4e106cc5c100ce0220960a43577ed5b2b510c393efc205dd4d6
MD5 956ad9f08c9cbe0245aa5e8b79bf77e2
BLAKE2b-256 f8a53de1675a4db44c690fdf97e1fd1d4e9d81bea32b88df1c833d28035795ca

See more details on using hashes here.

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

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

File details

Details for the file json_leases_to_unbound-0.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for json_leases_to_unbound-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 983c20f1af6894c50451fd5b4cd9a84b294c5e51883b9af2e4a6a1581e16cf98
MD5 df76c96729b6c452d98b4a060185e936
BLAKE2b-256 926d3452f89b64aae64d2f4a99623e8c285a7f8a67540048f14303c03a6b899b

See more details on using hashes here.

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

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