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.
Internet speed
Run a simple receive/download and send/upload throughput check against Cloudflare's edge:
ip-reporter speed
Use ip-reporter speed --format json for structured output. The defaults
download 10 MB and upload 2 MB. This is a quick single-transfer estimate, not a
full multi-connection benchmark; sample sizes can be changed with
--download-bytes and --upload-bytes.
The same check is available from Python:
from ip_reporter import check_internet_speed
speed = check_internet_speed()
print(speed.download_mbps, speed.upload_mbps)
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
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 ip_reporter-0.1.3.tar.gz.
File metadata
- Download URL: ip_reporter-0.1.3.tar.gz
- Upload date:
- Size: 195.7 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f54688038d5c9c9da7b61b21d628eb712be91cd0fbc8b343679b54d0d8bc698
|
|
| MD5 |
d499d3696c3f360a216f84c2817bb6c8
|
|
| BLAKE2b-256 |
7eabda279506ece26427ae104cc64fbc595bf92bed338cb3c5572360fc772aaf
|
File details
Details for the file ip_reporter-0.1.3-py3-none-any.whl.
File metadata
- Download URL: ip_reporter-0.1.3-py3-none-any.whl
- Upload date:
- Size: 26.7 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b89602c8bb2523d0a823e28672aa3b4d3aa5cf8261d51139cc1af597e8970eae
|
|
| MD5 |
c8465fceb9783d6fe94df42d0ca0ef43
|
|
| BLAKE2b-256 |
a76aaf3a8cd6e7444d33bc6a311a8d007f6ea1ab8774bfbf36f7947a04beb4bb
|