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()