-
Notifications
You must be signed in to change notification settings - Fork 210
feat: add announce addrs in host, #1250 #1268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
f30adef
382db94
dd5e502
0c7d715
589560b
2825a32
e0b8161
c3134d6
a993c2c
701faa8
32213e9
3c9549d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Announce Addresses Example | ||
|
|
||
| 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. | ||
|
|
||
| ## Overview | ||
|
|
||
| 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: | ||
|
|
||
| ```sh | ||
| pip install -e . | ||
| ``` | ||
|
|
||
| ### Node A (listener) | ||
|
|
||
| Start the listener with announce addresses: | ||
|
|
||
| ```sh | ||
| 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: | ||
|
|
||
| ```sh | ||
| 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're 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| """ | ||
| Announce Addresses Example for py-libp2p | ||
|
|
||
| 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. | ||
|
|
||
| Node A (listener): | ||
| python announce_addrs.py --listen-port 9001 \ | ||
| --announce /dns4/example.ngrok-free.app/tcp/9001 /ip4/1.2.3.4/tcp/4001 | ||
|
|
||
| Node B (dialer): | ||
| python announce_addrs.py --listen-port 9002 \ | ||
| --dial /dns4/example.ngrok-free.app/tcp/9001/p2p/<PEER_ID_OF_A> | ||
| """ | ||
|
|
||
| import argparse | ||
| import logging | ||
| import secrets | ||
|
|
||
| import multiaddr | ||
| import trio | ||
|
|
||
| from libp2p import new_host | ||
| from libp2p.crypto.secp256k1 import create_new_key_pair | ||
| from libp2p.peer.peerinfo import info_from_p2p_addr | ||
|
|
||
| logging.basicConfig( | ||
| level=logging.INFO, | ||
| format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", | ||
| ) | ||
| logger = logging.getLogger("announce_addrs_example") | ||
|
|
||
| # Silence noisy libraries | ||
| logging.getLogger("multiaddr").setLevel(logging.WARNING) | ||
|
|
||
|
|
||
| async def run_listener(port: int, announce_addrs: list[str]) -> None: | ||
| """Start a node that listens locally and announces external addresses.""" | ||
| key_pair = create_new_key_pair(secrets.token_bytes(32)) | ||
|
|
||
| listen_addrs = [multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")] | ||
|
|
||
| parsed_announce = [multiaddr.Multiaddr(a) for a in announce_addrs] | ||
|
|
||
| host = new_host(key_pair=key_pair, announce_addrs=parsed_announce) | ||
|
|
||
| async with host.run(listen_addrs=listen_addrs): | ||
| peer_id = host.get_id().to_string() | ||
|
|
||
| logger.info("Node started") | ||
| logger.info(f"Peer ID: {peer_id}") | ||
|
|
||
| logger.info("Transport (local) addresses:") | ||
| for addr in host.get_transport_addrs(): | ||
| logger.info(f" {addr}") | ||
|
|
||
| logger.info("Announced (public) addresses:") | ||
| for addr in host.get_addrs(): | ||
| logger.info(f" {addr}") | ||
|
|
||
| print(f"\nPeer ID: {peer_id}") | ||
| print("\nTo connect from another node, run:") | ||
| for addr in host.get_addrs(): | ||
| print(f" python announce_addrs.py --listen-port 9002 --dial {addr}") | ||
|
|
||
| print("\nPress Ctrl+C to exit.") | ||
| await trio.sleep_forever() | ||
|
|
||
|
|
||
| async def run_dialer(port: int, dial_addr: str) -> None: | ||
| """Start a node and connect to a remote peer.""" | ||
| key_pair = create_new_key_pair(secrets.token_bytes(32)) | ||
|
|
||
| listen_addrs = [multiaddr.Multiaddr(f"/ip4/0.0.0.0/tcp/{port}")] | ||
|
|
||
| host = new_host(key_pair=key_pair) | ||
|
|
||
| async with host.run(listen_addrs=listen_addrs): | ||
| logger.info(f"Dialer started, peer ID: {host.get_id().to_string()}") | ||
|
|
||
| ma = multiaddr.Multiaddr(dial_addr) | ||
| peer_info = info_from_p2p_addr(ma) | ||
|
|
||
| logger.info(f"Connecting to {peer_info.peer_id}...") | ||
| await host.connect(peer_info) | ||
| logger.info(f"Successfully connected to {peer_info.peer_id}") | ||
|
|
||
| print(f"\nConnected to peer: {peer_info.peer_id}") | ||
| print("Press Ctrl+C to exit.") | ||
| await trio.sleep_forever() | ||
|
|
||
|
|
||
| def main() -> None: | ||
| parser = argparse.ArgumentParser( | ||
| description="Announce Addresses Example", | ||
| formatter_class=argparse.RawDescriptionHelpFormatter, | ||
| ) | ||
| parser.add_argument( | ||
| "--listen-port", | ||
| type=int, | ||
| default=9001, | ||
| help="Local TCP port to listen on (default: 9001)", | ||
| ) | ||
| parser.add_argument( | ||
| "--announce", | ||
| nargs="+", | ||
| help="Announce addresses (e.g. /dns4/example.ngrok-free.app/tcp/443)", | ||
| ) | ||
| parser.add_argument( | ||
| "--dial", | ||
| type=str, | ||
| help="Full multiaddr of remote peer to connect (must include /p2p/<peerID>)", | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| if args.dial: | ||
| trio.run(run_dialer, args.listen_port, args.dial) | ||
| elif args.announce: | ||
| trio.run(run_listener, args.listen_port, args.announce) | ||
| else: | ||
| parser.error("Provide --announce to listen, or --dial to connect to a peer.") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,9 +93,6 @@ | |
| background_trio_service, | ||
| ) | ||
| from libp2p.transport.quic.connection import QUICConnection | ||
| from libp2p.utils.multiaddr_utils import ( | ||
| join_multiaddrs, | ||
| ) | ||
| import libp2p.utils.paths | ||
| from libp2p.utils.varint import ( | ||
| read_length_prefixed_protobuf, | ||
|
|
@@ -193,6 +190,7 @@ def __init__( | |
| bootstrap_allow_ipv6: bool = False, | ||
| bootstrap_dns_timeout: float = 10.0, | ||
| bootstrap_dns_max_retries: int = 3, | ||
| announce_addrs: Sequence[multiaddr.Multiaddr] | None = None, | ||
| ) -> None: | ||
| """ | ||
| Initialize a BasicHost instance. | ||
|
|
@@ -253,6 +251,9 @@ def __init__( | |
| ) | ||
| self.psk = psk | ||
|
|
||
| # Address announcement configuration | ||
| self._announce_addrs = list(announce_addrs) if announce_addrs else None | ||
|
||
|
|
||
| # Cache a signed-record if the local-node in the PeerStore | ||
| envelope = create_signed_peer_record( | ||
| self.get_id(), | ||
|
|
@@ -349,13 +350,22 @@ def get_transport_addrs(self) -> list[multiaddr.Multiaddr]: | |
|
|
||
| def get_addrs(self) -> list[multiaddr.Multiaddr]: | ||
| """ | ||
| Return all the multiaddr addresses this host is listening to. | ||
| Return the multiaddr addresses this host advertises to peers. | ||
|
|
||
| If ``announce_addrs`` was provided, those replace listen addresses | ||
| entirely. Otherwise listen addresses are used | ||
|
|
||
| Note: This method appends the /p2p/{peer_id} suffix to the addresses. | ||
| Use get_transport_addrs() for raw transport addresses. | ||
| """ | ||
| p2p_part = multiaddr.Multiaddr(f"/p2p/{self.get_id()!s}") | ||
| return [join_multiaddrs(addr, p2p_part) for addr in self.get_transport_addrs()] | ||
|
|
||
| if self._announce_addrs is not None: | ||
| addrs = list(self._announce_addrs) | ||
| else: | ||
| addrs = self.get_transport_addrs() | ||
|
|
||
| return [addr.encapsulate(p2p_part) for addr in addrs] | ||
|
||
|
|
||
| def get_connected_peers(self) -> list[ID]: | ||
| """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Added ``announce_addrs`` support to ``BasicHost`` so nodes behind NAT or | ||
| reverse proxies can advertise their publicly reachable addresses instead of | ||
| local listen addresses. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A better, more discoverable place for this file would be to reformat and move it to
examples.announce_addrs.rst. That way it would get included in the docs. You can follow other examples there andliteralincludethe example code at the bottom. Seeexamples.chat.rstas an example.