Skip to main content

Reliable WAN/LAN IP discovery and Cloudflare DNS synchronization

Project description

ip-reporter

ip-reporter is a dependency-free Python package and CLI for discovering local LAN addresses, public WAN addresses, and public addresses obtained through HTTP/2 or HTTP/3. It can synchronize one or many A/AAAA records in Cloudflare.

The import name is ip_reporter; the installed command is ip-reporter.

Install with uv

Install the command as a uv tool:

uv tool install ip-reporter
ip-reporter --scope wan --family ipv4

Use it as a library dependency:

uv add ip-reporter

For HTTP/2 and HTTP/3 probing on macOS, install Homebrew curl. The package rejects /usr/bin/curl on macOS so the system curl cannot silently disable those probes.

brew install curl
export IP_REPORTER_CURL="$(brew --prefix curl)/bin/curl"

The selected curl must report HTTP2 or HTTP3 in curl --version depending on the requested probe. HTTP/3 requires a curl build with HTTP/3 support.

CLI

With no subcommand, the CLI performs discovery. The default family is IPv4.

# Public IPv4; text output includes the scope, family, and transport.
ip-reporter --scope wan --family ipv4

# LAN IPv4 and IPv6.
ip-reporter discover --scope lan --family ipv4 --family ipv6

# Force HTTP/3 over IPv4 and IPv6.
ip-reporter --scope wan --family h3ipv4 --family h3ipv6

# Structured output for scripts.
ip-reporter --scope both --family ipv4 --family ipv6 --format json

# Print only address values, one per line.
ip-reporter --scope wan --family ipv4 --format values

WAN probing has independent failover endpoints. The defaults include two providers for IPv4, two for IPv6, and two candidates for each HTTP/3 family. Override them with repeated --wan-endpoint options or with the Python RemoteEndpoint API.

Cloudflare DNS

Use an API token with the DNS Write permission. Prefer environment variables so credentials do not enter shell history:

export CLOUDFLARE_API_TOKEN="..."
export CLOUDFLARE_ZONE_ID="..."

Update one existing record:

ip-reporter dns update \
  --record home.example.com \
  --family ipv4

Update many names and both address families:

ip-reporter dns update \
  --record home.example.com \
  --record vpn.example.com \
  --family ipv4 \
  --family ipv6 \
  --ttl 60 \
  --not-proxied

Existing records are updated with PATCH and unchanged values are skipped. Missing records are not created unless --create-missing is supplied. Stale records are not deleted. Use --dry-run to inspect discovery without changing Cloudflare.

If you have a zone name rather than an ID, pass --zone-name; the client can resolve it through the Cloudflare API. Relative names and @ are accepted when the zone name is supplied:

ip-reporter dns update \
  --zone-name example.com \
  --record home \
  --family ipv4 \
  --create-missing

h3ipv4 writes an A record and h3ipv6 writes an AAAA record. The HTTP/3 transport is discovery metadata; the DNS type is determined from the resulting IP address.

Python API

The structured API is suitable for another package, a scheduler, or a daemon:

from ip_reporter import AddressFamily, IPReporter

reporter = IPReporter()
report = reporter.discover(
    scope="wan",
    families=(AddressFamily.IPV4, AddressFamily.IPV6),
)

for address in report.wan:
    print(address.value, address.family.value, address.protocol.value)

# Convenience methods return only strings.
wan_ipv4 = reporter.get_wan(families=(AddressFamily.IPV4,))
lan_ipv6 = reporter.get_lan(families=(AddressFamily.IPV6,))

The discovery dependencies are injectable, which makes applications and tests independent of the network:

from ip_reporter import AddressDiscovery, AddressFamily, IPReporter, RemoteEndpoint

class Probe:
    def fetch_ip(self, url, family):
        return ("203.0.113.10", "http2")

reporter = IPReporter(
    AddressDiscovery(
        wan_probe=Probe(),
        endpoints=(RemoteEndpoint("https://probe.example/ip", (AddressFamily.IPV4,)),),
    )
)

Synchronize Cloudflare from the same service:

from ip_reporter import AddressFamily, IPReporter

updates = IPReporter().update_cloudflare_dns(
    ["home.example.com", "vpn.example.com"],
    zone_id="your-zone-id",
    families=(AddressFamily.IPV4, AddressFamily.IPV6),
    create_missing=True,
)
print(updates.as_dict())

CloudflareClient.from_env() uses CLOUDFLARE_API_TOKEN, or the legacy CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY pair. A custom HTTP transport can be injected into CloudflareClient for testing.

Prefect IPv4 task

Install the optional Prefect integration, then use the task in a deployment or call it directly. It persists the last IPv4 to the defined state path and only calls Cloudflare when the newly discovered value differs. The default state path is ~/.local/state/ip-reporter/wan_ipv4.txt; provide an absolute path in production so the state survives task-run environments.

uv add 'ip-reporter[prefect]'
export CLOUDFLARE_API_TOKEN="..."
export CLOUDFLARE_ZONE_ID="..."
from ip_reporter.prefect import fetch_ipv4_and_sync_cloudflare

result = fetch_ipv4_and_sync_cloudflare(
    ["home.example.com", "vpn.example.com"],
    ipv4_state_path="/var/lib/ip-reporter/home-ipv4.txt",
    create_missing=True,
)
print(result)

The state file is atomically written only after Cloudflare succeeds. If the task fails, its next run will retry the DNS synchronization rather than assume the address was updated.

Build and publish

From a checkout:

uv sync --extra dev
uv run pytest
uv build
uv publish

Set UV_PUBLISH_TOKEN before uv publish, or configure another uv-supported PyPI credential method. Bump project.version in pyproject.toml for each release.

Cloudflare API details: DNS record create, DNS record update.

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

ip_reporter-0.1.2.tar.gz (189.9 kB view details)

Uploaded Source

Built Distribution

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

ip_reporter-0.1.2-py3-none-any.whl (24.5 kB view details)

Uploaded Python 3

File details

Details for the file ip_reporter-0.1.2.tar.gz.

File metadata

  • Download URL: ip_reporter-0.1.2.tar.gz
  • Upload date:
  • Size: 189.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ip_reporter-0.1.2.tar.gz
Algorithm Hash digest
SHA256 0f2e131be6e06c46c0b6f1e8b9bcd92443eee5da83b4f2ecb9931c74ce807ad9
MD5 cc5c3c328f12256a756e397aedf8e4ca
BLAKE2b-256 88fcc456f816c4b4fff0079bf108ea724c1d9a6231196110bba1753b260f8e00

See more details on using hashes here.

File details

Details for the file ip_reporter-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: ip_reporter-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 24.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ip_reporter-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4c9d99983ce4acc4e72b9e0e7b2a0259ab979c19e8e8e17db7b63efd7c36043e
MD5 d3d99f392753e79fc303148aa2eec2de
BLAKE2b-256 e31af910a66f26feaae6eb780ff01487a3cf225007641d4d48dd13037a30a267

See more details on using hashes here.

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