|
| 1 | +import argparse |
| 2 | +import logging |
| 3 | +from pathlib import Path |
| 4 | + |
| 5 | +import multiaddr |
| 6 | +import trio |
| 7 | + |
| 8 | +import libp2p |
| 9 | +from libp2p import ( |
| 10 | + generate_new_ed25519_identity, |
| 11 | + load_keypair, |
| 12 | + new_host, |
| 13 | + save_keypair, |
| 14 | +) |
| 15 | +from libp2p.abc import INetStream |
| 16 | +from libp2p.crypto.x25519 import create_new_key_pair as create_new_x25519_key_pair |
| 17 | +from libp2p.custom_types import ( |
| 18 | + TProtocol, |
| 19 | +) |
| 20 | +from libp2p.identity.identify.identify import ( |
| 21 | + ID as IDENTIFY_PROTOCOL_ID, |
| 22 | + identify_handler_for, |
| 23 | +) |
| 24 | +from libp2p.peer.peerinfo import ( |
| 25 | + info_from_p2p_addr, |
| 26 | +) |
| 27 | +from libp2p.security.noise.transport import ( |
| 28 | + PROTOCOL_ID as NOISE_PROTOCOL_ID, |
| 29 | + Transport as NoiseTransport, |
| 30 | +) |
| 31 | +from libp2p.security.tls.transport import ( |
| 32 | + PROTOCOL_ID as TLS_PROTOCOL_ID, |
| 33 | + TLSTransport, |
| 34 | +) |
| 35 | +import libp2p.utils |
| 36 | +import libp2p.utils.paths |
| 37 | + |
| 38 | +# Configure logging to show debug logs |
| 39 | +root = logging.getLogger() |
| 40 | +root.handlers.clear() |
| 41 | +root.setLevel(logging.WARNING) |
| 42 | + |
| 43 | +handler = logging.StreamHandler() |
| 44 | +handler.setLevel(logging.DEBUG) |
| 45 | +handler.setFormatter(logging.Formatter("[%(levelname)s] %(name)s: %(message)s")) |
| 46 | + |
| 47 | +for name in [ |
| 48 | + "root", |
| 49 | + "libp2p.network.basic_host", |
| 50 | + "libp2p.security.tls", |
| 51 | + "libp2p.autotls.acme", |
| 52 | + "libp2p.autotls.broker", |
| 53 | +]: |
| 54 | + logger = logging.getLogger(name) |
| 55 | + logger.setLevel(logging.INFO) |
| 56 | + logger.addHandler(handler) |
| 57 | + logger.propagate = False |
| 58 | + |
| 59 | + |
| 60 | +PING_PROTOCOL_ID = TProtocol("/ipfs/ping/1.0.0") |
| 61 | +PING_LENGTH = 32 |
| 62 | +RESP_TIMEOUT = 60 |
| 63 | +PSK = "dffb7e3135399a8b1612b2aaca1c36a3a8ac2cd0cca51ceeb2ced87d308cac6d" |
| 64 | +DIRECTORY = {} |
| 65 | +ACME_DIRECTORY_URL = "https://acme-staging-v02.api.letsencrypt.org/directory" |
| 66 | +PEER_ID_AUTH_SCHEME = "libp2p-PeerID=" |
| 67 | + |
| 68 | + |
| 69 | +async def handle_ping(stream: INetStream) -> None: |
| 70 | + while True: |
| 71 | + try: |
| 72 | + payload = await stream.read(PING_LENGTH) |
| 73 | + peer_id = stream.muxed_conn.peer_id |
| 74 | + if payload is not None: |
| 75 | + print(f"received ping from {peer_id}") |
| 76 | + |
| 77 | + await stream.write(payload) |
| 78 | + print(f"responded with pong to {peer_id}") |
| 79 | + |
| 80 | + except Exception: |
| 81 | + await stream.reset() |
| 82 | + break |
| 83 | + |
| 84 | + |
| 85 | +async def send_ping(stream: INetStream) -> None: |
| 86 | + try: |
| 87 | + payload = b"\x01" * PING_LENGTH |
| 88 | + print(f"sending ping to {stream.muxed_conn.peer_id}") |
| 89 | + |
| 90 | + await stream.write(payload) |
| 91 | + |
| 92 | + with trio.fail_after(RESP_TIMEOUT): |
| 93 | + response = await stream.read(PING_LENGTH) |
| 94 | + |
| 95 | + if response == payload: |
| 96 | + print(f"received pong from {stream.muxed_conn.peer_id}") |
| 97 | + |
| 98 | + except Exception as e: |
| 99 | + print(f"error occurred : {e}") |
| 100 | + |
| 101 | + |
| 102 | +async def run(port: int, destination: str, new: int, transport: str, tls: int) -> None: |
| 103 | + from libp2p.utils.address_validation import ( |
| 104 | + find_free_port, |
| 105 | + get_available_interfaces, |
| 106 | + ) |
| 107 | + |
| 108 | + # Create a libp2p-forge directory for persisting keys and certificates |
| 109 | + # Currently the config is for 2 peers exchanging ping/pong |
| 110 | + base = Path("libp2p-forge") |
| 111 | + (base / "peer1").mkdir(parents=True, exist_ok=True) |
| 112 | + (base / "peer2").mkdir(parents=True, exist_ok=True) |
| 113 | + |
| 114 | + enable_autotls = True |
| 115 | + if tls == 1: |
| 116 | + enable_autotls = False |
| 117 | + |
| 118 | + if port <= 0: |
| 119 | + port = find_free_port() |
| 120 | + |
| 121 | + if transport == "tcp": |
| 122 | + listen_addrs = get_available_interfaces(port) |
| 123 | + if transport == "ws": |
| 124 | + listen_addrs = [multiaddr.Multiaddr(f"/ip4/127.0.0.1/tcp/{port}/ws")] |
| 125 | + |
| 126 | + if new == 1: |
| 127 | + libp2p.utils.paths.ED25519_PATH = Path("libp2p-forge/peer2/ed25519.pem") |
| 128 | + libp2p.utils.paths.AUTOTLS_CERT_PATH = Path( |
| 129 | + "libp2p-forge/peer2/autotls-cert.pem" |
| 130 | + ) |
| 131 | + libp2p.utils.paths.AUTOTLS_KEY_PATH = Path("libp2p-forge/peer2/autotls-key.pem") |
| 132 | + |
| 133 | + key_pair = load_keypair() |
| 134 | + |
| 135 | + if key_pair: |
| 136 | + logging.info("Loaded existing key-pair") |
| 137 | + else: |
| 138 | + logging.info("Generated new key-pair...") |
| 139 | + key_pair = generate_new_ed25519_identity() |
| 140 | + save_keypair(key_pair) |
| 141 | + |
| 142 | + noise_key_pair = create_new_x25519_key_pair() |
| 143 | + noise_transport = NoiseTransport(key_pair, noise_privkey=noise_key_pair.private_key) |
| 144 | + tls_transport = TLSTransport(key_pair, enable_autotls=enable_autotls) |
| 145 | + |
| 146 | + security_options = { |
| 147 | + TLS_PROTOCOL_ID: tls_transport, |
| 148 | + NOISE_PROTOCOL_ID: noise_transport, |
| 149 | + } |
| 150 | + |
| 151 | + host = new_host( |
| 152 | + key_pair=key_pair, |
| 153 | + listen_addrs=listen_addrs, |
| 154 | + sec_opt=security_options, |
| 155 | + enable_autotls=enable_autotls, |
| 156 | + ) |
| 157 | + |
| 158 | + base_identify_handler = identify_handler_for(host, use_varint_format=False) |
| 159 | + async with host.run(listen_addrs=listen_addrs), trio.open_nursery() as nursery: |
| 160 | + # Start the peer-store cleanup task |
| 161 | + nursery.start_soon(host.get_peerstore().start_cleanup_task, 60) |
| 162 | + |
| 163 | + if not destination: |
| 164 | + host.set_stream_handler(IDENTIFY_PROTOCOL_ID, base_identify_handler) |
| 165 | + host.set_stream_handler(PING_PROTOCOL_ID, handle_ping) |
| 166 | + |
| 167 | + # Replace/remove this hardcoded IP when running on you own servers |
| 168 | + # Check this function to more info: `inititate_autotls_procedure` |
| 169 | + if enable_autotls: |
| 170 | + await host.initiate_autotls_procedure(public_ip="13.126.88.127") |
| 171 | + |
| 172 | + all_addrs = host.get_addrs() |
| 173 | + print("Listener ready, listening on:\n") |
| 174 | + for addr in all_addrs: |
| 175 | + print(f"{addr}") |
| 176 | + |
| 177 | + all_addrs = host.get_addrs() |
| 178 | + if all_addrs: |
| 179 | + print( |
| 180 | + f"\nRun this from the same folder in another console:\n\n" |
| 181 | + f"autotls-demo -d {all_addrs[0]} -new 1 -t {transport} -tls {tls}\n" |
| 182 | + ) |
| 183 | + else: |
| 184 | + print("\nWarning: No listening addresses available") |
| 185 | + print("Waiting for incoming connection...") |
| 186 | + |
| 187 | + else: |
| 188 | + all_addrs = host.get_addrs() |
| 189 | + print("Listener ready, listening on:\n") |
| 190 | + for addr in all_addrs: |
| 191 | + print(f"{addr}") |
| 192 | + print("\n\n") |
| 193 | + |
| 194 | + host.set_stream_handler(IDENTIFY_PROTOCOL_ID, base_identify_handler) |
| 195 | + |
| 196 | + # Replace/remove this hardcoded IP when running on you own servers |
| 197 | + # Check this function to more info: `inititate_autotls_procedure` |
| 198 | + if enable_autotls: |
| 199 | + await host.initiate_autotls_procedure(public_ip="13.126.88.127") |
| 200 | + |
| 201 | + maddr = multiaddr.Multiaddr(destination) |
| 202 | + info = info_from_p2p_addr(maddr) |
| 203 | + await host.connect(info) |
| 204 | + stream = await host.new_stream(info.peer_id, [PING_PROTOCOL_ID]) |
| 205 | + |
| 206 | + nursery.start_soon(send_ping, stream) |
| 207 | + return |
| 208 | + |
| 209 | + await trio.sleep_forever() |
| 210 | + |
| 211 | + |
| 212 | +def main() -> None: |
| 213 | + description = """ |
| 214 | + This program demonstrates a simple p2p ping application using libp2p. |
| 215 | + To use it, first run 'python ping.py -p <PORT>', where <PORT> is the port number. |
| 216 | + Then, run another instance with 'python ping.py -p <ANOTHER_PORT> -d <DESTINATION>', |
| 217 | + where <DESTINATION> is the multiaddress of the previous listener host. |
| 218 | + """ |
| 219 | + |
| 220 | + example_maddr = ( |
| 221 | + "/ip4/[HOST_IP]/tcp/8000/p2p/QmQn4SwGkDZKkUEpBRBvTmheQycxAHJUNmVEnjA2v1qe8Q" |
| 222 | + ) |
| 223 | + |
| 224 | + parser = argparse.ArgumentParser(description=description) |
| 225 | + parser.add_argument("-p", "--port", default=0, type=int, help="source port number") |
| 226 | + |
| 227 | + parser.add_argument( |
| 228 | + "-d", |
| 229 | + "--destination", |
| 230 | + type=str, |
| 231 | + help=f"destination multiaddr string, e.g. {example_maddr}", |
| 232 | + ) |
| 233 | + |
| 234 | + parser.add_argument("-new", "--new", default=0, type=int, help="Run client") |
| 235 | + |
| 236 | + parser.add_argument( |
| 237 | + "-tls", "--tls", default=0, type=int, help="Run with self-signed TLS handshake" |
| 238 | + ) |
| 239 | + |
| 240 | + parser.add_argument( |
| 241 | + "-t", |
| 242 | + "--transport", |
| 243 | + default="tcp", |
| 244 | + type=str, |
| 245 | + help="Choose the transport layer for ping TCP/WS", |
| 246 | + ) |
| 247 | + |
| 248 | + args = parser.parse_args() |
| 249 | + |
| 250 | + try: |
| 251 | + trio.run( |
| 252 | + run, *(args.port, args.destination, args.new, args.transport, args.tls) |
| 253 | + ) |
| 254 | + except KeyboardInterrupt: |
| 255 | + pass |
| 256 | + |
| 257 | + |
| 258 | +if __name__ == "__main__": |
| 259 | + main() |
0 commit comments