Announce Addresses

See also

Same topic, without the step-by-step example: Advertising dialable addresses.

This example demonstrates how to use announce addresses so that a node behind NAT or a reverse proxy (e.g., ngrok) advertises its publicly reachable address instead of its local listen address.

When running a libp2p node behind NAT or a reverse proxy, other nodes cannot reach it using the internal listen address. By specifying announce addresses, you can tell peers about your externally accessible addresses instead.

Usage

First, ensure you have installed the necessary dependencies from the root of the repository:

$ python -m pip install -e .

Node A (listener) – start the listener with announce addresses:

$ python examples/announce_addrs/announce_addrs.py --listen-port 9001 \
    --announce /dns4/example.ngrok-free.app/tcp/9001 /ip4/1.2.3.4/tcp/4001

Node B (dialer) – connect to the listener using its announced address and peer ID:

$ python examples/announce_addrs/announce_addrs.py --listen-port 9002 \
    --dial /dns4/example.ngrok-free.app/tcp/9001/p2p/<PEER_ID_OF_A>

Notes on NAT and Reverse Proxies

This pattern is useful when:

  • Your node is behind a NAT that performs port forwarding from an external IP to your local machine.

  • You are using a reverse proxy like ngrok that exposes your local port to the internet.

  • You need to advertise different addresses for external vs. internal connectivity.

By announcing the correct external addresses, peers will successfully dial your node regardless of their network position.

Automatic discovery vs. explicit announce addresses

py-libp2p also ships with an ObservedAddrManager that automatically discovers the host’s externally observed addresses through the Identify protocol. Once enough distinct peer groups confirm the same external address, it is appended to the output of get_addrs() – no manual configuration is required for the common NAT / EC2 case (see issue #1250).

announce_addrs takes priority over observed addresses: when it is set it acts as a static AddrsFactory (matching go-libp2p’s applyAddrsFactory behaviour), so only the explicitly announced list is advertised. Observations are still recorded internally – for example to feed get_nat_type() – but they are not emitted by get_addrs when a static list has been provided.

Use announce_addrs when you already know the exact public address(es) you want peers to dial (e.g. a reverse proxy hostname such as ngrok). Rely on automatic observed-address discovery otherwise.

The full source code for this example is below:

  1"""
  2Announce Addresses Example for py-libp2p
  3
  4Demonstrates how to use announce addresses so that a node behind NAT
  5or a reverse proxy (e.g. ngrok) advertises its publicly reachable
  6address instead of its local listen address.
  7
  8Node A (listener):
  9    python announce_addrs.py --listen-port 9001 \
 10        --announce /dns4/example.ngrok-free.app/tcp/9001 /ip4/1.2.3.4/tcp/4001
 11
 12Node B (dialer):
 13    python announce_addrs.py --listen-port 9002 \
 14        --dial /dns4/example.ngrok-free.app/tcp/9001/p2p/<PEER_ID_OF_A>
 15"""
 16
 17import argparse
 18import logging
 19import secrets
 20
 21import multiaddr
 22import trio
 23
 24from libp2p import new_host
 25from libp2p.crypto.secp256k1 import create_new_key_pair
 26from libp2p.peer.peerinfo import info_from_p2p_addr
 27
 28logging.basicConfig(
 29    level=logging.INFO,
 30    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 31)
 32logger = logging.getLogger("announce_addrs_example")
 33
 34# Silence noisy libraries
 35logging.getLogger("multiaddr").setLevel(logging.WARNING)
 36
 37
 38async def run_listener(port: int, announce_addrs: list[str]) -> None:
 39    """Start a node that listens locally and announces external addresses."""
 40    key_pair = create_new_key_pair(secrets.token_bytes(32))
 41
 42    listen_addrs = [multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")]
 43
 44    parsed_announce = [multiaddr.Multiaddr(a) for a in announce_addrs]
 45
 46    host = new_host(key_pair=key_pair, announce_addrs=parsed_announce)
 47
 48    async with host.run(listen_addrs=listen_addrs):
 49        peer_id = host.get_id().to_string()
 50
 51        logger.info("Node started")
 52        logger.info(f"Peer ID: {peer_id}")
 53
 54        logger.info("Transport (local) addresses:")
 55        for addr in host.get_transport_addrs():
 56            logger.info(f"  {addr}")
 57
 58        logger.info("Announced (public) addresses:")
 59        for addr in host.get_addrs():
 60            logger.info(f"  {addr}")
 61
 62        print(f"\nPeer ID: {peer_id}")
 63        print("\nTo connect from another node, run:")
 64        for addr in host.get_addrs():
 65            print(f"  python announce_addrs.py --listen-port 9002 --dial {addr}")
 66
 67        print("\nPress Ctrl+C to exit.")
 68        await trio.sleep_forever()
 69
 70
 71async def run_dialer(port: int, dial_addr: str) -> None:
 72    """Start a node and connect to a remote peer."""
 73    key_pair = create_new_key_pair(secrets.token_bytes(32))
 74
 75    listen_addrs = [multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")]
 76
 77    host = new_host(key_pair=key_pair)
 78
 79    async with host.run(listen_addrs=listen_addrs):
 80        logger.info(f"Dialer started, peer ID: {host.get_id().to_string()}")
 81
 82        ma = multiaddr.Multiaddr(dial_addr)
 83        peer_info = info_from_p2p_addr(ma)
 84
 85        logger.info(f"Connecting to {peer_info.peer_id}...")
 86        await host.connect(peer_info)
 87        logger.info(f"Successfully connected to {peer_info.peer_id}")
 88
 89        print(f"\nConnected to peer: {peer_info.peer_id}")
 90        print("Press Ctrl+C to exit.")
 91        await trio.sleep_forever()
 92
 93
 94def main() -> None:
 95    parser = argparse.ArgumentParser(
 96        description="Announce Addresses Example",
 97        formatter_class=argparse.RawDescriptionHelpFormatter,
 98    )
 99    parser.add_argument(
100        "--listen-port",
101        type=int,
102        default=9001,
103        help="Local TCP port to listen on (default: 9001)",
104    )
105    parser.add_argument(
106        "--announce",
107        nargs="+",
108        help="Announce addresses (e.g. /dns4/example.ngrok-free.app/tcp/443)",
109    )
110    parser.add_argument(
111        "--dial",
112        type=str,
113        help="Full multiaddr of remote peer to connect (must include /p2p/<peerID>)",
114    )
115
116    args = parser.parse_args()
117
118    if args.dial:
119        trio.run(run_dialer, args.listen_port, args.dial)
120    elif args.announce:
121        trio.run(run_listener, args.listen_port, args.announce)
122    else:
123        parser.error("Provide --announce to listen, or --dial to connect to a peer.")
124
125
126if __name__ == "__main__":
127    main()