跳到主要内容

使用 rust libp2p 的打孔机制来克服防火墙和 NAT

rust libp2p打洞教程

本教程将向您展示如何使用 libp2p 的打孔机制来克服防火墙和 NAT 本教程需要:

  • 内网电脑即平时用的笔记本(win10)
  • 服务器(腾讯云windows版win10)

设置启动服务器

打孔需要一个公共中继节点,以便两个专用节点协调其打孔方式。为此,我们需要在互联网上的某个地方有一个公共服务器。如果您还没有,任何云提供商VM都可以。

直接在服务器上或在本地计算机上编译示例中继服务器:

cargo build

target/debug/libp2p-hole-punching.exe复制到服务器启动

libp2p-hole-punching.exe  --port 8080 --secret-key-seed 0

这里输入图片描述

服务端代码

main.rs


// Copyright 2020 Parity Technologies (UK) Ltd.
// Copyright 2021 Protocol Labs.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use clap::Parser;
use futures::executor::block_on;
use futures::stream::StreamExt;
use libp2p::core::upgrade;
use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent};
use libp2p::multiaddr::Protocol;
use libp2p::ping::{Ping, PingConfig, PingEvent};
use libp2p::relay::v2::relay::{self, Relay};
use libp2p::swarm::{Swarm, SwarmEvent};
use libp2p::tcp::TcpConfig;
use libp2p::Transport;
use libp2p::{identity, NetworkBehaviour, PeerId};
use libp2p::{noise, Multiaddr};
use std::error::Error;
use std::net::{Ipv4Addr, Ipv6Addr};

fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();

let opt = Opt::parse();
println!("opt: {:?}", opt);

// Create a static known PeerId based on given secret
let local_key: identity::Keypair = generate_ed25519(opt.secret_key_seed);
let local_peer_id = PeerId::from(local_key.public());
println!("Local peer id: {:?}", local_peer_id);

let tcp_transport = TcpConfig::new();

let noise_keys = noise::Keypair::<noise::X25519Spec>::new()
.into_authentic(&local_key)
.expect("Signing libp2p-noise static DH keypair failed.");

let transport = tcp_transport
.upgrade(upgrade::Version::V1)
.authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated())
.multiplex(libp2p_yamux::YamuxConfig::default())
.boxed();

let behaviour = Behaviour {
relay: Relay::new(local_peer_id, Default::default()),
ping: Ping::new(PingConfig::new()),
identify: Identify::new(IdentifyConfig::new(
"/TODO/0.0.1".to_string(),
local_key.public(),
)),
};

let mut swarm = Swarm::new(transport, behaviour, local_peer_id);

// Listen on all interfaces
let listen_addr = Multiaddr::empty()
.with(match opt.use_ipv6 {
Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED),
_ => Protocol::from(Ipv4Addr::UNSPECIFIED),
})
.with(Protocol::Tcp(opt.port));
swarm.listen_on(listen_addr)?;

block_on(async {
loop {
match swarm.next().await.expect("Infinite Stream.") {
SwarmEvent::Behaviour(Event::Relay(event)) => {
println!("{:?}", event)
}
SwarmEvent::NewListenAddr { address, .. } => {
println!("Listening on {:?}", address);
}
_ => {}
}
}
})
}

#[derive(NetworkBehaviour)]
#[behaviour(out_event = "Event", event_process = false)]
struct Behaviour {
relay: Relay,
ping: Ping,
identify: Identify,
}

#[derive(Debug)]
enum Event {
Ping(PingEvent),
Identify(IdentifyEvent),
Relay(relay::Event),
}

impl From<PingEvent> for Event {
fn from(e: PingEvent) -> Self {
Event::Ping(e)
}
}

impl From<IdentifyEvent> for Event {
fn from(e: IdentifyEvent) -> Self {
Event::Identify(e)
}
}

impl From<relay::Event> for Event {
fn from(e: relay::Event) -> Self {
Event::Relay(e)
}
}

fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair {
let mut bytes = [0u8; 32];
bytes[0] = secret_key_seed;

let secret_key = identity::ed25519::SecretKey::from_bytes(&mut bytes)
.expect("this returns `Err` only if the length is wrong; the length is correct; qed");
identity::Keypair::Ed25519(secret_key.into())
}

#[derive(Debug, Parser)]
#[clap(name = "libp2p relay")]
struct Opt {
/// Determine if the relay listen on ipv6 or ipv4 loopback address. the default is ipv4
#[clap(long)]
use_ipv6: Option<bool>,

/// Fixed value to generate deterministic peer id
#[clap(long)]
secret_key_seed: u8,

/// The port used to listen on all interfaces
#[clap(long)]
port: u16,
}



Cargo.toml

[package]
name = "libp2p-hole-punching"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = "0.3.1"
libp2p = { version = "0.45.1",features = ["identify", "relay", "ping", "noise", "plaintext", "tcp-async-io"] }
clap = {version = "3.1.6", features = ["derive"]}
env_logger = "0.9.0"
libp2p-yamux="0.37.0"


测试

ping 服务器外网ip
telnet 服务器ip 端口

使用 libp2p-lookup 进行连接 注:安装libp2p-lookup

cargo install libp2p-lookup

libp2p-lookup测试结果

C:\Users\demo>libp2p-lookup direct --address /ip4/服务器ip/tcp/8080
warning: invalid logging spec 'info libp2p-hole-puncing-client.exe --secret-key-seed 1 --mode listen --relay-address /ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN', ignoring it (too many '/'s)
Lookup for peer with id PeerId("12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN") succeeded.

Protocol version: "/TODO/0.0.1"
Agent version: "rust-libp2p/0.36.1"
Observed address: "/ip4/xxxxxx/tcp/9943"
Listen addresses:
- "/ip4/服务器ip/tcp/8080"
- "/ip4/xxxxxx/tcp/8080"
- "/ip4/127.0.0.1/tcp/8080"
Protocols:
- "/libp2p/circuit/relay/0.2.0/hop"
- "/ipfs/ping/1.0.0"
- "/ipfs/id/1.0.0"
- "/ipfs/id/push/1.0.0"

设置启动客户端

cargo build

target/debug/libp2p-hole-puncing-client.exe启动

>  RUST_LOG=info libp2p-hole-puncing-client.exe --secret-key-seed 1 --mode listen --relay-address /ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN #服务端生成的PeerId



[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Local peer id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X")
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxx/tcp/9080"
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxx/tcp/9080"
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/127.0.0.1/tcp/9080"
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Told relay its public address.
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Relay told us our public address: "/ip4/xxxxxx/tcp/9080"
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Relay accepted our reservation request.
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxx/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"

新开一个cmd窗口运行


> RUST_LOG=info libp2p-hole-puncing-client.exe --secret-key-seed 1 --mode listen --relay-address /ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN #服务端生成的PeerId


[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Local peer id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X")
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxxx/tcp/9080"
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxxx/tcp/9080"
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/127.0.0.1/tcp/9080"
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Told relay its public address.
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Relay told us our public address: "/ip4/xxxxxx/tcp/9080"
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Relay accepted our reservation request.
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Listening on "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"

此时服务端cmd显示信息

test>libp2p-hole-punching.exe  --port 8080 --secret-key-seed 0
opt: Opt { use_ipv6: None, secret_key_seed: 0, port: 8080 }
Local peer id: PeerId("12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN")
Listening on "/ip4/xxxxx/tcp/8080"
Listening on "/ip4/xxxxx/tcp/8080"
ReservationReqAccepted { src_peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"), renewed: false }
CircuitReqAccepted { src_peer_id: PeerId("12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3"), dst_peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") }
CircuitClosed { src_peer_id: PeerId("12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3"), dst_peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"), error: Some(Kind(ConnectionAborted)) }

客户端1cmd 显示信息


> RUST_LOG=info libp2p-hole-puncing-client.exe --secret-key-seed 1 --mode listen --relay-address /ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Local peer id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X")
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxxx/tcp/9080"
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxxx/tcp/9080"
[2022-06-20T09:25:46Z INFO libp2p_hole_puncing_client] Listening on "/ip4/127.0.0.1/tcp/9080"
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Told relay its public address.
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Relay told us our public address: "/ip4/xxxxxx/tcp/9080"
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Relay accepted our reservation request.
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Listening on "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"
[2022-06-20T09:30:53Z INFO libp2p_hole_puncing_client] InboundCircuitEstablished { src_peer_id: PeerId("12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"), limit: Some(Limit { duration: Some(120s), data_in_bytes: Some(131072) }) }
[2022-06-20T09:30:54Z INFO libp2p_hole_puncing_client] Established connection to PeerId("12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3") via Listener { local_addr: "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit", send_back_addr: "/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN" }
[2022-06-20T09:30:54Z INFO libp2p_hole_puncing_client] InitiatedDirectConnectionUpgrade { remote_peer_id: PeerId("12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3"), local_relayed_addr: "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit" }
[2022-06-20T09:30:54Z INFO libp2p_hole_puncing_client] Received { peer_id: PeerId("12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3"), info: IdentifyInfo { public_key: Ed25519(PublicKey(compressed): 6b79c57e6a95239282c4818e96112f3f3a401ba97a564c23852a3f1ea5fc), protocol_version: "/TODO/0.0.1", agent_version: "rust-libp2p/0.36.1", listen_addrs: ["/ip4/xxxxxx/tcp/9127", "/ip4/xxxxxx/tcp/9127", "/ip4/xxxxxx/tcp/9127", "/ip4/127.0.0.1/tcp/9127"], protocols: ["/libp2p/circuit/relay/0.2.0/stop", "/ipfs/ping/1.0.0",
"/ipfs/id/1.0.0", "/ipfs/id/push/1.0.0", "/libp2p/dcutr"], observed_addr: "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X" } }
[2022-06-20T09:30:54Z INFO libp2p_hole_puncing_client] Sent { peer_id: PeerId("12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3") }

客户端2cmd显示信息


> RUST_LOG=info .\libp2p-hole-puncing-client.exe --secret-key-seed 2 --mode dial --relay-address /ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN --remote-peer-id 12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X
[2022-06-20T09:30:24Z INFO libp2p_hole_puncing_client] Local peer id: PeerId("12D3KooWH3uVF6wv47WnArKHk5p6cvgCJEb74UTmxztmQDc298L3")
[2022-06-20T09:30:24Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxxx/tcp/9127"
[2022-06-20T09:30:24Z INFO libp2p_hole_puncing_client] Listening on "/ip4/xxxxxx/tcp/9127"
[2022-06-20T09:30:24Z INFO libp2p_hole_puncing_client] Listening on "/ip4/127.0.0.1/tcp/9127"
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Told relay its public address.
[2022-06-20T09:30:52Z INFO libp2p_hole_puncing_client] Relay told us our public address: "/ip4/xxxxxx/tcp/9127"
[2022-06-20T09:30:53Z INFO libp2p_hole_puncing_client] OutboundCircuitEstablished { relay_peer_id: PeerId("12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN"), limit: None }
[2022-06-20T09:30:54Z INFO libp2p_hole_puncing_client] Established connection to PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") via Dialer { address: "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X", role_override: Dialer }
[2022-06-20T09:30:54Z INFO libp2p_hole_puncing_client] Sent { peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X") }
[2022-06-20T09:30:54Z INFO libp2p_hole_puncing_client] Received { peer_id: PeerId("12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"), info: IdentifyInfo { public_key: Ed25519(PublicKey(compressed): cecc157dc1ddd7295951c29888f095adb944d1b73d696e6df65d683bd4fc), protocol_version: "/TODO/0.0.1", agent_version: "rust-libp2p/0.36.1", listen_addrs: ["/ip4/xxxxxx/tcp/9080", "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X", "/ip4/xxxxxx/tcp/9080", "/ip4/xxxxxx/tcp/9080", "/ip4/127.0.0.1/tcp/9080", "/ip4/服务器ip/tcp/8080/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN/p2p-circuit/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X"], protocols: ["/libp2p/circuit/relay/0.2.0/stop", "/ipfs/ping/1.0.0", "/ipfs/id/1.0.0", "/ipfs/id/push/1.0.0", "/libp2p/dcutr"], observed_addr: "/p2p/12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN" } }

附客户端代码

// Copyright 2021 Protocol Labs.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use clap::Parser;
use futures::executor::block_on;
use futures::future::FutureExt;



use futures::stream::StreamExt;
use libp2p::core::multiaddr::{Multiaddr, Protocol};
use libp2p::core::transport::OrTransport;
use libp2p::core::upgrade;
use libp2p::dcutr;
use libp2p::dns::DnsConfig;
use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent, IdentifyInfo};
use libp2p::noise;
use libp2p::ping::{Ping, PingConfig, PingEvent};
use libp2p::relay::v2::client::{self, Client};
use libp2p::swarm::{SwarmBuilder, SwarmEvent};
use libp2p::tcp::TcpConfig;
use libp2p::Transport;
use libp2p::{identity, NetworkBehaviour, PeerId};
use log::info;
use std::convert::TryInto;
use std::error::Error;
use std::net::Ipv4Addr;
use std::str::FromStr;

#[derive(Debug, Parser)]
#[clap(name = "libp2p DCUtR client")]
struct Opts {
/// The mode (client-listen, client-dial).
#[clap(long)]
mode: Mode,

/// Fixed value to generate deterministic peer id.
#[clap(long)]
secret_key_seed: u8,

/// The listening address
#[clap(long)]
relay_address: Multiaddr,

/// Peer ID of the remote peer to hole punch to.
#[clap(long)]
remote_peer_id: Option<PeerId>,
}

#[derive(Debug, Parser, PartialEq)]
enum Mode {
Dial,
Listen,
}

impl FromStr for Mode {
type Err = String;
fn from_str(mode: &str) -> Result<Self, Self::Err> {
match mode {
"dial" => Ok(Mode::Dial),
"listen" => Ok(Mode::Listen),
_ => Err("Expected either 'dial' or 'listen'".to_string()),
}
}
}

fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();

let opts = Opts::parse();

let local_key = generate_ed25519(opts.secret_key_seed);
let local_peer_id = PeerId::from(local_key.public());
info!("Local peer id: {:?}", local_peer_id);

let (relay_transport, client) = Client::new_transport_and_behaviour(local_peer_id);

let noise_keys = noise::Keypair::<noise::X25519Spec>::new()
.into_authentic(&local_key)
.expect("Signing libp2p-noise static DH keypair failed.");

let transport = OrTransport::new(
relay_transport,
block_on(DnsConfig::system(TcpConfig::new().port_reuse(true))).unwrap(),
)
.upgrade(upgrade::Version::V1)
.authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated())
.multiplex(libp2p_yamux::YamuxConfig::default())
.boxed();

#[derive(NetworkBehaviour)]
#[behaviour(out_event = "Event", event_process = false)]
struct Behaviour {
relay_client: Client,
ping: Ping,
identify: Identify,
dcutr: dcutr::behaviour::Behaviour,
}

#[derive(Debug)]
enum Event {
Ping(PingEvent),
Identify(IdentifyEvent),
Relay(client::Event),
Dcutr(dcutr::behaviour::Event),
}

impl From<PingEvent> for Event {
fn from(e: PingEvent) -> Self {
Event::Ping(e)
}c
}

impl From<IdentifyEvent> for Event {
fn from(e: IdentifyEvent) -> Self {
Event::Identify(e)
}
}

impl From<client::Event> for Event {
fn from(e: client::Event) -> Self {
Event::Relay(e)
}
}

impl From<dcutr::behaviour::Event> for Event {
fn from(e: dcutr::behaviour::Event) -> Self {
Event::Dcutr(e)
}
}

let behaviour = Behaviour {
relay_client: client,
ping: Ping::new(PingConfig::new()),
identify: Identify::new(IdentifyConfig::new(
"/TODO/0.0.1".to_string(),
local_key.public(),
)),
dcutr: dcutr::behaviour::Behaviour::new(),
};

let mut swarm = SwarmBuilder::new(transport, behaviour, local_peer_id)
.dial_concurrency_factor(10_u8.try_into().unwrap())
.build();

swarm
.listen_on(
Multiaddr::empty()
.with("0.0.0.0".parse::<Ipv4Addr>().unwrap().into())
.with(Protocol::Tcp(0)),
)
.unwrap();

// Wait to listen on all interfaces.
block_on(async {
let mut delay = futures_timer::Delay::new(std::time::Duration::from_secs(1)).fuse();
loop {
futures::select! {
event = swarm.next() => {
match event.unwrap() {
SwarmEvent::NewListenAddr { address, .. } => {
info!("Listening on {:?}", address);
}
event => panic!("{:?}", event),
}
}
_ = delay => {
// Likely listening on all interfaces now, thus continuing by breaking the loop.
break;
}
}
}
});

// Connect to the relay server. Not for the reservation or relayed connection, but to (a) learn
// our local public address and (b) enable a freshly started relay to learn its public address.
swarm.dial(opts.relay_address.clone()).unwrap();
block_on(async {
let mut learned_observed_addr = false;
let mut told_relay_observed_addr = false;

loop {
match swarm.next().await.unwrap() {
SwarmEvent::NewListenAddr { .. } => {}
SwarmEvent::Dialing { .. } => {}
SwarmEvent::ConnectionEstablished { .. } => {}
SwarmEvent::Behaviour(Event::Ping(_)) => {}
SwarmEvent::Behaviour(Event::Identify(IdentifyEvent::Sent { .. })) => {
info!("Told relay its public address.");
told_relay_observed_addr = true;
}
SwarmEvent::Behaviour(Event::Identify(IdentifyEvent::Received {
info: IdentifyInfo { observed_addr, .. },
..
})) => {
info!("Relay told us our public address: {:?}", observed_addr);
learned_observed_addr = true;
}
event => panic!("{:?}", event),
}

if learned_observed_addr && told_relay_observed_addr {
break;
}
}
});

match opts.mode {
Mode::Dial => {
swarm
.dial(
opts.relay_address
.with(Protocol::P2pCircuit)
.with(Protocol::P2p(opts.remote_peer_id.unwrap().into())),
)
.unwrap();
}
Mode::Listen => {
swarm
.listen_on(opts.relay_address.with(Protocol::P2pCircuit))
.unwrap();
}
}

block_on(async {
loop {
match swarm.next().await.unwrap() {
SwarmEvent::NewListenAddr { address, .. } => {
info!("Listening on {:?}", address);
}
SwarmEvent::Behaviour(Event::Relay(client::Event::ReservationReqAccepted {
..
})) => {
assert!(opts.mode == Mode::Listen);
info!("Relay accepted our reservation request.");
}
SwarmEvent::Behaviour(Event::Relay(event)) => {
info!("{:?}", event)
}
SwarmEvent::Behaviour(Event::Dcutr(event)) => {
info!("{:?}", event)
}
SwarmEvent::Behaviour(Event::Identify(event)) => {
info!("{:?}", event)
}
SwarmEvent::Behaviour(Event::Ping(_)) => {}
SwarmEvent::ConnectionEstablished {
peer_id, endpoint, ..
} => {
info!("Established connection to {:?} via {:?}", peer_id, endpoint);
}
SwarmEvent::OutgoingConnectionError { peer_id, error } => {
info!("Outgoing connection error to {:?}: {:?}", peer_id, error);
}
_ => {}
}
}
})
}

fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair {
let mut bytes = [0u8; 32];
bytes[0] = secret_key_seed;

let secret_key = identity::ed25519::SecretKey::from_bytes(&mut bytes)
.expect("this returns `Err` only if the length is wrong; the length is correct; qed");
identity::Keypair::Ed25519(secret_key.into())
}

Cargo.toml

[package]
name = "libp2p-hole-puncing-client"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
futures = "0.3.1"
libp2p = { version = "0.45.1",features = ["dcutr", "relay", "plaintext", "identify", "tcp-async-io", "ping", "noise", "dns-async-std"] }
clap = {version = "3.1.6", features = ["derive"]}
env_logger = "0.9.0"
libp2p-yamux="0.37.0"
log = "0.4"
futures-timer = "3.0"

备注

此文是参照https://docs.rs/libp2p/latest/libp2p/tutorials/hole_punching/index.html教程 的实践记录 rust libp2p github https://github.com/libp2p/rust-libp2p