ip-discovery 0.3.0

A lightweight, high-performance library for detecting public IP addresses via DNS, HTTP, and STUN protocols
Documentation

ip-discovery

Crates.io docs.rs MIT/Apache-2.0

Detect your public IP address using DNS, STUN, or HTTP — with built-in fallback across trusted providers.

Why ip-discovery?

Most machines don't know their own public IP. If you're behind NAT, a load balancer, or a cloud VPC, your OS only sees a private address like 10.x.x.x or 192.168.x.x. This library solves that — reliably, fast, and with zero configuration.

Common use cases:

  • Self-hosted servers with dynamic IPs — Your home server or office NAS gets a new IP every time the ISP rotates it. Use ip-discovery to detect the change and update your DNS record (dynamic DNS), notify clients, or refresh firewall rules — automatically.

  • WebRTC / P2P connection setup — When building WebRTC applications, you need your public IP to generate SDP offers/answers and ICE candidates. ip-discovery uses the same STUN protocol that browsers use, giving you the public-facing address for direct peer connections without relying on a browser environment.

  • NAT traversal & hole punching — Building a peer-to-peer system (game server, file sharing, VPN)? You need to know your public IP and the type of NAT you're behind before you can punch through it.

  • Server self-registration — Microservices or edge nodes that spin up in dynamic cloud environments (auto-scaling groups, spot instances) and need to register their public address with a service registry or coordination layer.

  • Security & audit logging — Record the public IP of the machine at the time of an event for compliance or forensics. Use Consensus strategy to cross-verify across multiple providers and guard against a single provider being spoofed.

  • CLI diagnostics — Quickly check "what IP does the internet see me as?" during debugging, without opening a browser or remembering which curl endpoint to hit.

Why not just curl an IP-echo service?

Calling a single HTTP endpoint works for a quick manual check, but falls short in production:

HTTP IP-echo services ip-discovery
Single point of failure If that one service is down or slow, you get nothing Automatic fallback across 9 providers and 3 protocols
Rate limiting Many free services aggressively throttle or block automated requests DNS and STUN are lightweight UDP queries — far less likely to be throttled than HTTP APIs
Latency Full TCP + TLS handshake every time (~200–500ms) DNS & STUN use raw UDP — typically <50ms, 2–3× faster
Result verification You trust one provider blindly — it could return stale data or be spoofed Consensus strategy cross-checks across multiple providers
IPv6 support Depends on the endpoint; many only return IPv4 First-class IPv4 and IPv6 support across DNS and STUN
Dependency in code Needs shell-out or an HTTP client just to get an IP Embeddable Rust library, no HTTP dependency needed (DNS + STUN only)
Offline-friendly Requires an HTTP-capable environment / TLS stack DNS and STUN work in minimal environments with just UDP

💡 Note: Some strict enterprise networks block outbound UDP entirely. In those environments, DNS and STUN won't work. Enable the http feature to add HTTP-based providers as a fallback — the library will automatically try them if UDP-based providers fail.

CLI Tool — ipd

A command-line tool powered by this library. Get your public IP in one command:

$ ipd
203.0.113.42

Install

Homebrew (macOS):

brew install zer0horizon/tap/ipd

Shell (macOS & Linux):

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/zer0horizon/ip-discovery/releases/latest/download/ipd-installer.sh | sh

PowerShell (Windows):

powershell -ExecutionPolicy Bypass -c "irm https://github.com/zer0horizon/ip-discovery/releases/latest/download/ipd-installer.ps1 | iex"

Cargo:

cargo install ipd

CLI Usage

ipd                    # Plain IP output
ipd -4                 # IPv4 only
ipd -6                 # IPv6 only
ipd -f json            # JSON output
ipd -f verbose         # Verbose output with provider info
ipd -s race            # Race all providers, return fastest
ipd -p dns -p stun     # Use only DNS and STUN protocols
ipd -t 5               # 5 second timeout

Library

Features

  • DNS and STUN via raw UDP sockets (zero network library dependencies)
  • HTTP/HTTPS via reqwest (optional)
  • Built-in providers from Google, Cloudflare, AWS, and OpenDNS
  • IPv4 and IPv6
  • Sequential fallback, race, or consensus strategies
  • Custom providers via the Provider trait
  • Async, built on tokio

Usage

Add to your Cargo.toml:

[dependencies]
ip-discovery = "0.2"
tokio = { version = "1", features = ["full"] }

Then:

use ip_discovery::get_ip;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result = get_ip().await?;
    println!("{} via {} in {:?}", result.ip, result.provider, result.latency);
    Ok(())
}

You can also request a specific IP version:

use ip_discovery::{get_ipv4, get_ipv6};

let v4 = get_ipv4().await?;
let v6 = get_ipv6().await?;

Configuration

The defaults (Cloudflare STUN → Cloudflare DNS → Google STUN/DNS → OpenDNS; 10s timeout) work well for most cases. If you need more control:

use ip_discovery::{Config, Strategy, Protocol, BuiltinProvider, get_ip_with};
use std::time::Duration;

// DNS only, race all DNS providers
let config = Config::builder()
    .protocols(&[Protocol::Dns])
    .strategy(Strategy::Race)
    .timeout(Duration::from_secs(5))
    .build();

let result = get_ip_with(config).await?;
// Pick specific providers
let config = Config::builder()
    .providers(&[
        BuiltinProvider::CloudflareDns,
        BuiltinProvider::GoogleStun,
    ])
    .build();
// Consensus — require at least 2 providers to agree
let config = Config::builder()
    .strategy(Strategy::Consensus { min_agree: 2 })
    .build();

Strategies

Strategy Description
First (default) Try providers in order, return first success
Race Query all concurrently, return fastest
Consensus { min_agree } Require N providers to agree on the same IP

Providers

All built-in providers are from tier-1 infrastructure companies:

Provider Protocol IPv4 IPv6
Google STUN (stun.l.google.com) STUN
Google STUN 1 (stun1.l.google.com) STUN
Google STUN 2 (stun2.l.google.com) STUN
Cloudflare STUN (stun.cloudflare.com) STUN
Google DNS (o-o.myaddr.l.google.com) DNS
Cloudflare DNS (whoami.cloudflare) DNS
OpenDNS (myip.opendns.com) DNS
Cloudflare HTTP (1.1.1.1/cdn-cgi/trace) HTTP
AWS (checkip.amazonaws.com) HTTP

Cargo Features

Feature Default Description
dns DNS detection (raw UDP, no extra deps)
stun STUN detection (raw UDP, no extra deps)
http HTTP detection (pulls in reqwest + rustls)
all Enable all protocols (dns + stun + http)
native-tls Use OS-native TLS instead of rustls (requires http)

By default, only DNS and STUN are enabled — zero network library dependencies, fast compile times. To also use HTTP providers:

ip-discovery = { version = "0.2", features = ["http"] }

Or enable everything:

ip-discovery = { version = "0.2", features = ["all"] }

Performance

STUN and DNS use raw UDP — no TLS handshake — so they're typically 2–3× faster than HTTP. Default provider order prioritizes UDP-based protocols with IPv4 + IPv6 support first, then falls back to IPv4-only HTTP providers.

💡 Tip: Latency varies significantly by region and network environment. Run the benchmark on your own infrastructure to find the optimal provider and strategy for your use case.

# Run the benchmark to find the best config for your network
cargo run --example benchmark --all-features

Logging

Uses tracing for diagnostics:

tracing_subscriber::fmt()
    .with_env_filter("ip_discovery=debug")
    .init();

Examples

cargo run --example simple
cargo run --example custom_providers
cargo run --example benchmark

MSRV

Rust 1.85 or later.

License

Licensed under either of Apache License, Version 2.0 or MIT License, at your option.