net: Perform full p2p code cleanup and improve certain pieces.

Notable changes:

* Rewritten transport protocols into Dialer and Listener (Nym is TODO)

  This simplifies using the transports a lot, as can be seen for example
  in src/rpc, and generally around the p2p library. It also defines features
  for each transport (all of which are enabled by default). We drop the
  socks client for Tor and Nym and use first-class support with the Arti Tor
  library, and nym-sphinx/nym-websockets (to be used with nym-client).

* Outbound session healing

  The outbound session will now poll and try to fill all the requested
  slots more efficiently, and if needed, will activate peer discovery to
  find more peers if we can't connect to any known ones. Also if we're
  unable to connect to any, we shall drop them from our set.

  Additionally, transport mixing is enabled by default, so when we're
  allowing transport mixing, and we use Tor, we will also be able to connect
  to other transports that Tor can connect to (e.g. tcp://).

* Unix socket transport dropped

  We haven't been using this, and it seems we're not going down this path,
  so the code has been obsoleted and removed.

* TLS session verification

  We fully verify server and client TLS certificates upon connection so
  we're able to perform TLS1.3 with forward secrecy.

* lilith pruning

  lilith now periodically prunes known peers from its sets if it's unable
  to connect to them.
This commit is contained in:
parazyd
2023-06-26 15:48:38 +02:00
parent d070b7792b
commit 55ee919906
69 changed files with 4894 additions and 4553 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@
.#* .#*
*.profraw *.profraw
/vendor/*
/target/* /target/*
/tmp/* /tmp/*

2531
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -71,11 +71,15 @@ futures = {version = "0.3.28", optional = true}
smol = {version = "1.3.0", optional = true} smol = {version = "1.3.0", optional = true}
# Networking # Networking
futures-rustls = {version = "0.24.0", features = ["dangerous_configuration"], optional = true} async-rustls = {version = "0.4.0", features = ["dangerous_configuration"], optional = true}
iprange = {version = "0.6.7", optional = true} iprange = {version = "0.6.7", optional = true}
ipnet = {version = "2.7.2", optional = true} ipnet = {version = "2.7.2", optional = true}
socket2 = {version = "0.5.3", optional = true, features = ["all"]} socket2 = {version = "0.5.3", optional = true, features = ["all"]}
# Pluggable Transports
arti-client = {version = "0.9.1", default-features = false, features = ["async-std", "rustls", "onion-service-client"], optional = true}
# TODO: nym ( Read this to figure out impl https://github.com/ChainSafe/rust-libp2p-nym )
# TLS cert utilities # TLS cert utilities
ed25519-compact = {version = "2.0.4", optional = true} ed25519-compact = {version = "2.0.4", optional = true}
rcgen = {version = "0.10.0", optional = true} rcgen = {version = "0.10.0", optional = true}
@@ -87,6 +91,7 @@ bs58 = {version = "0.5.0", optional = true}
hex = {version = "0.4.3", optional = true} hex = {version = "0.4.3", optional = true}
serde_json = {version = "1.0.96", optional = true} serde_json = {version = "1.0.96", optional = true}
serde = {version = "1.0.164", features = ["derive"], optional = true} serde = {version = "1.0.164", features = ["derive"], optional = true}
semver = {version = "1.0.17", optional = true}
structopt = {version= "0.3.26", optional = true} structopt = {version= "0.3.26", optional = true}
structopt-toml = {version= "0.5.1", optional = true} structopt-toml = {version= "0.5.1", optional = true}
toml = {version = "0.7.4", optional = true} toml = {version = "0.7.4", optional = true}
@@ -111,12 +116,6 @@ indicatif = {version = "0.17.5", optional = true}
simplelog = {version = "0.12.1", optional = true} simplelog = {version = "0.12.1", optional = true}
ripemd = {version = "0.1.3", optional = true} ripemd = {version = "0.1.3", optional = true}
# Websockets
async-tungstenite = {version = "0.22.2", optional = true}
# socks5
fast-socks5 = {version = "0.4.3", optional = true}
# Crypto # Crypto
rand = {version = "0.8.5", optional = true} rand = {version = "0.8.5", optional = true}
blake3 = {version = "1.4.0", features = ["rayon"], optional = true} blake3 = {version = "1.4.0", features = ["rayon"], optional = true}
@@ -131,12 +130,15 @@ wasmer-compiler-singlepass = {version = "3.3.0", optional = true}
wasmer-middlewares = {version = "3.3.0", optional = true} wasmer-middlewares = {version = "3.3.0", optional = true}
# Wallet management # Wallet management
sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"], optional = true} rusqlite = {version = "0.29.0", optional = true}
# Blockchain store # Blockchain store
sled = {version = "0.34.7", optional = true} sled = {version = "0.34.7", optional = true}
sled-overlay = {version = "0.0.7", optional = true} sled-overlay = {version = "0.0.7", optional = true}
# Temporary version lock
curve25519-dalek = {version = "=4.0.0-rc.2", default-features = false, optional = true}
[dev-dependencies] [dev-dependencies]
clap = {version = "4.3.3", features = ["derive"]} clap = {version = "4.3.3", features = ["derive"]}
halo2_proofs = {version = "0.3.0", features = ["dev-graph", "gadget-traces", "sanity-checks"]} halo2_proofs = {version = "0.3.0", features = ["dev-graph", "gadget-traces", "sanity-checks"]}
@@ -147,6 +149,10 @@ prettytable-rs = "0.10.0"
# -----BEGIN LIBRARY FEATURES----- # -----BEGIN LIBRARY FEATURES-----
[features] [features]
p2p-transport-tcp = []
p2p-transport-tor = ["arti-client"]
p2p-transport-nym = []
async-runtime = [ async-runtime = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -156,7 +162,7 @@ async-runtime = [
blockchain = [ blockchain = [
"blake3", "blake3",
"bs58", # <-- remove after we get rid of json for notifications "bs58", # <-- TODO: remove after we get rid of json for notifications
"chrono", "chrono",
"crypto_api_chachapoly", "crypto_api_chachapoly",
"dashu", "dashu",
@@ -165,7 +171,6 @@ blockchain = [
"rand", "rand",
"sled", "sled",
"sled-overlay", "sled-overlay",
"sqlx",
"url", "url",
"async-runtime", "async-runtime",
@@ -204,8 +209,7 @@ event-graph = [
net = [ net = [
"ed25519-compact", "ed25519-compact",
"fast-socks5", "async-rustls",
"futures-rustls",
"hex", "hex",
"iprange", "iprange",
"ipnet", "ipnet",
@@ -215,6 +219,7 @@ net = [
"rcgen", "rcgen",
"rustls-pemfile", "rustls-pemfile",
"x509-parser", "x509-parser",
"semver",
"serde", "serde",
"serde_json", "serde_json",
"socket2", "socket2",
@@ -226,6 +231,10 @@ net = [
"darkfi-serial/url", "darkfi-serial/url",
"system", "system",
"util", "util",
"p2p-transport-tcp",
"p2p-transport-tor",
"p2p-transport-nym",
] ]
raft = [ raft = [
@@ -281,7 +290,7 @@ util = [
wallet = [ wallet = [
"async-std", "async-std",
"rand", "rand",
"sqlx", "rusqlite",
"darkfi-serial", "darkfi-serial",
"util", "util",
@@ -296,10 +305,6 @@ wasm-runtime = [
"darkfi-sdk", "darkfi-sdk",
] ]
websockets = [
"async-tungstenite",
]
zk = [ zk = [
"halo2_proofs", "halo2_proofs",
"halo2_gadgets", "halo2_gadgets",
@@ -333,5 +338,6 @@ path = "example/zk-inclusion-proof.rs"
required-features = ["zk"] required-features = ["zk"]
[patch.crates-io] [patch.crates-io]
arti-client = {git="https://gitlab.torproject.org/tpo/core/arti", rev="08d1155cb92568176d8b54b85ec5437dff112e01"}
halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v3"} halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v3"}
halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v3"} halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v3"}

View File

@@ -12,7 +12,7 @@ CARGO = cargo
#TARGET_PRFX = --target= #TARGET_PRFX = --target=
# Binaries to be built # Binaries to be built
BINS = drk darkfid ircd dnetview faucetd vanityaddr BINS = darkfid faucetd drk darkirc dnetview vanityaddr
# zkas dependencies # zkas dependencies
ZKASDEPS = \ ZKASDEPS = \
@@ -36,8 +36,7 @@ BINDEPS = \
all: $(BINS) all: $(BINS)
zkas: $(ZKASDEPS) zkas: $(ZKASDEPS)
$(CARGO) build $(TARGET_PRFX)$(RUST_TARGET) \ $(CARGO) build $(TARGET_PRFX)$(RUST_TARGET) --all-features --release --package $@
--all-features --release --package $@
cp -f target/$(RUST_TARGET)/release/$@ $@ cp -f target/$(RUST_TARGET)/release/$@ $@
$(PROOFS_BIN): zkas $(PROOFS_SRC) $(PROOFS_BIN): zkas $(PROOFS_SRC)
@@ -50,8 +49,7 @@ contracts: zkas
$(MAKE) -C src/contract/deployooor $(MAKE) -C src/contract/deployooor
$(BINS): contracts $(PROOFS_BIN) $(BINDEPS) $(BINS): contracts $(PROOFS_BIN) $(BINDEPS)
$(CARGO) build $(TARGET_PRFX)$(RUST_TARGET) \ $(CARGO) build $(TARGET_PRFX)$(RUST_TARGET) --all-features --release --package $@
--all-features --release --package $@
cp -f target/$(RUST_TARGET)/release/$@ $@ cp -f target/$(RUST_TARGET)/release/$@ $@
check: contracts $(PROOFS_BIN) check: contracts $(PROOFS_BIN)

View File

@@ -14,7 +14,7 @@ async-trait = "0.1.68"
blake3 = "1.4.0" blake3 = "1.4.0"
bs58 = "0.5.0" bs58 = "0.5.0"
ctrlc = { version = "3.4.0", features = ["termination"] } ctrlc = { version = "3.4.0", features = ["termination"] }
darkfi = {path = "../../", features = ["blockchain", "wallet", "rpc", "net"]} darkfi = {path = "../../", features = ["blockchain", "wallet", "rpc", "net", "zkas"]}
darkfi-sdk = {path = "../../src/sdk"} darkfi-sdk = {path = "../../src/sdk"}
darkfi-serial = {path = "../../src/serial"} darkfi-serial = {path = "../../src/serial"}
easy-parallel = "3.3.0" easy-parallel = "3.3.0"
@@ -23,7 +23,6 @@ serde_json = "1.0.96"
simplelog = "0.12.1" simplelog = "0.12.1"
sled = "0.34.7" sled = "0.34.7"
smol = "1.3.0" smol = "1.3.0"
sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]}
url = "2.4.0" url = "2.4.0"
# Argument parsing # Argument parsing

View File

@@ -49,7 +49,7 @@ use darkfi::{
server::{listen_and_serve, RequestHandler}, server::{listen_and_serve, RequestHandler},
}, },
util::path::expand_path, util::path::expand_path,
wallet::{walletdb::init_wallet, WalletPtr}, wallet::{WalletDb, WalletPtr},
Error, Result, Error, Result,
}; };
@@ -105,7 +105,7 @@ struct Args {
#[structopt(long, default_value = "8")] #[structopt(long, default_value = "8")]
/// Connection slots for the consensus protocol /// Connection slots for the consensus protocol
consensus_slots: u32, consensus_slots: usize,
#[structopt(long)] #[structopt(long)]
/// Connect to peer for the consensus protocol (repeatable flag) /// Connect to peer for the consensus protocol (repeatable flag)
@@ -137,7 +137,7 @@ struct Args {
#[structopt(long, default_value = "8")] #[structopt(long, default_value = "8")]
/// Connection slots for the syncing protocol /// Connection slots for the syncing protocol
sync_slots: u32, sync_slots: usize,
#[structopt(long)] #[structopt(long)]
/// Connect to peer for the syncing protocol (repeatable flag) /// Connect to peer for the syncing protocol (repeatable flag)
@@ -297,7 +297,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
.unwrap(); .unwrap();
// Initialize or load wallet // Initialize or load wallet
let wallet = init_wallet(&args.wallet_path, &args.wallet_pass).await?; let wallet = WalletDb::new(Some(expand_path(&args.wallet_path)?), &args.wallet_pass).await?;
// Initialize or open sled database // Initialize or open sled database
let db_path = let db_path =
@@ -357,14 +357,13 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
let sync_p2p = { let sync_p2p = {
info!("Registering block sync P2P protocols..."); info!("Registering block sync P2P protocols...");
let sync_network_settings = net::Settings { let sync_network_settings = net::Settings {
inbound: args.sync_p2p_accept, inbound_addrs: args.sync_p2p_accept,
outbound_connections: args.sync_slots, outbound_connections: args.sync_slots,
external_addr: args.sync_p2p_external, external_addrs: args.sync_p2p_external,
peers: args.sync_p2p_peer.clone(), peers: args.sync_p2p_peer.clone(),
seeds: args.sync_p2p_seed.clone(), seeds: args.sync_p2p_seed.clone(),
outbound_transports: net::settings::get_outbound_transports(args.sync_p2p_transports), allowed_transports: args.sync_p2p_transports,
localnet: args.localnet, localnet: args.localnet,
channel_log: args.channel_log,
..Default::default() ..Default::default()
}; };
@@ -401,16 +400,13 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
} else { } else {
info!("Registering consensus P2P protocols..."); info!("Registering consensus P2P protocols...");
let consensus_network_settings = net::Settings { let consensus_network_settings = net::Settings {
inbound: args.consensus_p2p_accept, inbound_addrs: args.consensus_p2p_accept,
outbound_connections: args.consensus_slots, outbound_connections: args.consensus_slots,
external_addr: args.consensus_p2p_external, external_addrs: args.consensus_p2p_external,
peers: args.consensus_p2p_peer.clone(), peers: args.consensus_p2p_peer.clone(),
seeds: args.consensus_p2p_seed.clone(), seeds: args.consensus_p2p_seed.clone(),
outbound_transports: net::settings::get_outbound_transports( allowed_transports: args.consensus_p2p_transports,
args.consensus_p2p_transports,
),
localnet: args.localnet, localnet: args.localnet,
channel_log: args.channel_log,
..Default::default() ..Default::default()
}; };
let p2p = net::P2p::new(consensus_network_settings).await; let p2p = net::P2p::new(consensus_network_settings).await;
@@ -457,8 +453,9 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
}) })
.detach(); .detach();
info!("Waiting for sync P2P outbound connections"); // TODO: I think this is not necessary anymore
sync_p2p.clone().unwrap().wait_for_outbound(ex.clone()).await?; //info!("Waiting for sync P2P outbound connections");
//sync_p2p.clone().unwrap().wait_for_outbound(ex.clone()).await?;
match block_sync_task(sync_p2p.clone().unwrap(), state.clone()).await { match block_sync_task(sync_p2p.clone().unwrap(), state.clone()).await {
Ok(()) => *darkfid.synced.lock().await = true, Ok(()) => *darkfid.synced.lock().await = true,
@@ -478,8 +475,9 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
}) })
.detach(); .detach();
info!("Waiting for consensus P2P outbound connections"); // TODO: I think this is not necessary anymore
consensus_p2p.clone().unwrap().wait_for_outbound(ex.clone()).await?; //info!("Waiting for consensus P2P outbound connections");
//consensus_p2p.clone().unwrap().wait_for_outbound(ex.clone()).await?;
info!("Starting consensus protocol task"); info!("Starting consensus protocol task");
let _ex = ex.clone(); let _ex = ex.clone();
@@ -493,13 +491,11 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
print!("\r"); print!("\r");
info!("Caught termination signal, cleaning up and exiting..."); info!("Caught termination signal, cleaning up and exiting...");
// TODO: STOP P2P NETS
info!("Flushing sled database..."); info!("Flushing sled database...");
let flushed_bytes = sled_db.flush_async().await?; let flushed_bytes = sled_db.flush_async().await?;
info!("Flushed {} bytes", flushed_bytes); info!("Flushed {} bytes", flushed_bytes);
info!("Closing wallet connection...");
wallet.conn.close().await;
info!("Closed wallet connection");
Ok(()) Ok(())
} }

View File

@@ -143,8 +143,9 @@ impl Darkfid {
} }
if let Some(sync_p2p) = &self.sync_p2p { if let Some(sync_p2p) = &self.sync_p2p {
if let Err(e) = sync_p2p.broadcast(tx.clone()).await { sync_p2p.broadcast(&tx).await;
error!("[RPC] tx.broadcast: Failed broadcasting transaction: {}", e); if sync_p2p.channels().lock().await.is_empty() {
error!("[RPC] tx.broadcast: Failed broadcasting tx, no connected channels");
return server_error(RpcError::TxBroadcastFail, id, None) return server_error(RpcError::TxBroadcastFail, id, None)
} }
} else { } else {

View File

@@ -18,7 +18,6 @@
use log::{debug, error}; use log::{debug, error};
use serde_json::{json, Value}; use serde_json::{json, Value};
use sqlx::Row;
use darkfi::{ use darkfi::{
rpc::jsonrpc::{ rpc::jsonrpc::{
@@ -51,6 +50,8 @@ impl Darkfid {
// --> {"jsonrpc": "2.0", "method": "wallet.query_row_single", "params": [...], "id": 1} // --> {"jsonrpc": "2.0", "method": "wallet.query_row_single", "params": [...], "id": 1}
// <-- {"jsonrpc": "2.0", "result": ["va", "lu", "es", ...], "id": 1} // <-- {"jsonrpc": "2.0", "result": ["va", "lu", "es", ...], "id": 1}
pub async fn wallet_query_row_single(&self, id: Value, params: &[Value]) -> JsonResult { pub async fn wallet_query_row_single(&self, id: Value, params: &[Value]) -> JsonResult {
todo!();
/* TODO: This will be abstracted away
// We need at least 3 params for something we want to fetch, and we want them in pairs. // We need at least 3 params for something we want to fetch, and we want them in pairs.
// Also the first param should be a String // Also the first param should be a String
if params.len() < 3 || params[1..].len() % 2 != 0 || !params[0].is_string() { if params.len() < 3 || params[1..].len() % 2 != 0 || !params[0].is_string() {
@@ -189,6 +190,7 @@ impl Darkfid {
} }
JsonResponse::new(json!(ret), id).into() JsonResponse::new(json!(ret), id).into()
*/
} }
// RPCAPI: // RPCAPI:
@@ -201,6 +203,8 @@ impl Darkfid {
// --> {"jsonrpc": "2.0", "method": "wallet.query_row_multi", "params": [...], "id": 1} // --> {"jsonrpc": "2.0", "method": "wallet.query_row_multi", "params": [...], "id": 1}
// <-- {"jsonrpc": "2.0", "result": [["va", "lu"], ["es", "es"], ...], "id": 1} // <-- {"jsonrpc": "2.0", "result": [["va", "lu"], ["es", "es"], ...], "id": 1}
pub async fn wallet_query_row_multi(&self, id: Value, params: &[Value]) -> JsonResult { pub async fn wallet_query_row_multi(&self, id: Value, params: &[Value]) -> JsonResult {
todo!();
/* TODO: This will be abstracted away
// We need at least 3 params for something we want to fetch, and we want them in pairs. // We need at least 3 params for something we want to fetch, and we want them in pairs.
// Also the first param (the query) should be a String. // Also the first param (the query) should be a String.
if params.len() < 3 || params[1..].len() % 2 != 0 || !params[0].is_string() { if params.len() < 3 || params[1..].len() % 2 != 0 || !params[0].is_string() {
@@ -313,6 +317,7 @@ impl Darkfid {
} }
JsonResponse::new(json!(ret), id).into() JsonResponse::new(json!(ret), id).into()
*/
} }
// RPCAPI: // RPCAPI:
@@ -322,6 +327,8 @@ impl Darkfid {
// --> {"jsonrpc": "2.0", "method": "wallet.exec_sql", "params": ["CREATE TABLE ..."], "id": 1} // --> {"jsonrpc": "2.0", "method": "wallet.exec_sql", "params": ["CREATE TABLE ..."], "id": 1}
// <-- {"jsonrpc": "2.0", "result": true, "id": 1} // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
pub async fn wallet_exec_sql(&self, id: Value, params: &[Value]) -> JsonResult { pub async fn wallet_exec_sql(&self, id: Value, params: &[Value]) -> JsonResult {
todo!();
/* TODO: This will be abstracted away
if params.is_empty() || !params[0].is_string() { if params.is_empty() || !params[0].is_string() {
return JsonError::new(InvalidParams, None, id).into() return JsonError::new(InvalidParams, None, id).into()
} }
@@ -423,5 +430,6 @@ impl Darkfid {
}; };
JsonResponse::new(json!(true), id).into() JsonResponse::new(json!(true), id).into()
*/
} }
} }

View File

@@ -15,7 +15,7 @@ darkfi-serial = {path = "../../src/serial"}
# Async # Async
smol = "1.3.0" smol = "1.3.0"
futures = "0.3.28" futures = "0.3.28"
futures-rustls = "0.24.0" async-rustls = "0.4.0"
rustls-pemfile = "1.0.2" rustls-pemfile = "1.0.2"
async-std = "1.12.0" async-std = "1.12.0"
async-trait = "0.1.68" async-trait = "0.1.68"

View File

@@ -40,10 +40,10 @@ outbound_connections=5
#peers = ["tls://127.0.0.1:26661"] #peers = ["tls://127.0.0.1:26661"]
## Seed nodes to connect to ## Seed nodes to connect to
seeds = ["tls://lilith0.dark.fi:26661", "tls://lilith1.dark.fi:26661"] seeds = ["tcp+tls://lilith0.dark.fi:26661", "tcp+tls://lilith1.dark.fi:26661"]
# Prefered transports for outbound connections # Prefered transports for outbound connections
outbound_transports = ["tls"] outbound_transports = ["tcp", "tcp+tls"]
## Only used for debugging. Compromises privacy when set. ## Only used for debugging. Compromises privacy when set.
#node_id = "foo" #node_id = "foo"

View File

@@ -18,12 +18,12 @@
use std::{fs::File, net::SocketAddr}; use std::{fs::File, net::SocketAddr};
use async_rustls::{rustls, TlsAcceptor};
use async_std::{ use async_std::{
net::TcpListener, net::TcpListener,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use futures::{io::BufReader, AsyncRead, AsyncReadExt, AsyncWrite}; use futures::{io::BufReader, AsyncRead, AsyncReadExt, AsyncWrite};
use futures_rustls::{rustls, TlsAcceptor};
use log::{error, info}; use log::{error, info};
use darkfi::{ use darkfi::{
@@ -177,7 +177,7 @@ impl IrcServer {
continue continue
} }
p2p.broadcast(event).await?; p2p.broadcast(&event).await;
} }
NotifierMsg::UpdateConfig => { NotifierMsg::UpdateConfig => {

View File

@@ -106,8 +106,7 @@ async fn realmain(settings: Args, executor: Arc<smol::Executor<'_>>) -> Result<(
let seen_inv = Seen::new(); let seen_inv = Seen::new();
// Check the version // Check the version
let mut net_settings = settings.net.clone(); let net_settings = settings.net.clone();
net_settings.app_version = Some(option_env!("CARGO_PKG_VERSION").unwrap_or("").to_string());
// New p2p // New p2p
let p2p = net::P2p::new(net_settings.into()).await; let p2p = net::P2p::new(net_settings.into()).await;

View File

@@ -130,8 +130,8 @@ async fn main() -> DnetViewResult<()> {
//debug!(target: "dnetview", "main() START"); //debug!(target: "dnetview", "main() START");
let args = Args::parse(); let args = Args::parse();
let log_level = get_log_level(args.verbose.into()); let log_level = get_log_level(args.verbose);
let log_config = get_log_config(); let log_config = get_log_config(args.verbose);
let log_file_path = expand_path(&args.log_path)?; let log_file_path = expand_path(&args.log_path)?;
if let Some(parent) = log_file_path.parent() { if let Some(parent) = log_file_path.parent() {

View File

@@ -27,6 +27,5 @@ smol = "1.3.0"
simplelog = "0.12.1" simplelog = "0.12.1"
signal-hook-async-std = "0.2.2" signal-hook-async-std = "0.2.2"
signal-hook = "0.3.15" signal-hook = "0.3.15"
sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]}
url = "2.4.0" url = "2.4.0"
rodio = {version = "0.17.1", default-features = false, features = ["minimp3"]} rodio = {version = "0.17.1", default-features = false, features = ["minimp3"]}

View File

@@ -484,8 +484,8 @@ async fn main() -> Result<()> {
let args = Args::parse(); let args = Args::parse();
if args.verbose > 0 { if args.verbose > 0 {
let log_level = get_log_level(args.verbose.into()); let log_level = get_log_level(args.verbose);
let log_config = get_log_config(); let log_config = get_log_config(args.verbose);
TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?; TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
} }

View File

@@ -133,7 +133,7 @@ struct Args {
#[structopt(long, default_value = "8")] #[structopt(long, default_value = "8")]
/// Connection slots for the syncing protocol /// Connection slots for the syncing protocol
sync_slots: u32, sync_slots: usize,
#[structopt(long)] #[structopt(long)]
/// Connect to seed for the syncing protocol (repeatable flag) /// Connect to seed for the syncing protocol (repeatable flag)
@@ -151,10 +151,6 @@ struct Args {
/// Enable localnet hosts /// Enable localnet hosts
localnet: bool, localnet: bool,
#[structopt(long)]
/// Enable channel log
channel_log: bool,
#[structopt(long)] #[structopt(long)]
/// Whitelisted cashier address (repeatable flag) /// Whitelisted cashier address (repeatable flag)
cashier_pub: Vec<String>, cashier_pub: Vec<String>,
@@ -719,14 +715,13 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
// P2P network. The faucet doesn't participate in consensus, so we only // P2P network. The faucet doesn't participate in consensus, so we only
// build the sync protocol. // build the sync protocol.
let network_settings = net::Settings { let network_settings = net::Settings {
inbound: args.sync_p2p_accept, inbound_addrs: args.sync_p2p_accept,
outbound_connections: args.sync_slots, outbound_connections: args.sync_slots,
external_addr: args.sync_p2p_external, external_addrs: args.sync_p2p_external,
peers: args.sync_p2p_peer.clone(), peers: args.sync_p2p_peer.clone(),
seeds: args.sync_p2p_seed.clone(), seeds: args.sync_p2p_seed.clone(),
outbound_transports: net::settings::get_outbound_transports(args.sync_p2p_transports), allowed_transports: args.sync_p2p_transports,
localnet: args.localnet, localnet: args.localnet,
channel_log: args.channel_log,
..Default::default() ..Default::default()
}; };
@@ -788,8 +783,9 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
}) })
.detach(); .detach();
info!("Waiting for sync P2P outbound connections"); // TODO: I think this is not needed anymore
sync_p2p.clone().wait_for_outbound(ex).await?; //info!("Waiting for sync P2P outbound connections");
//sync_p2p.clone().wait_for_outbound(ex).await?;
match block_sync_task(sync_p2p, state.clone()).await { match block_sync_task(sync_p2p, state.clone()).await {
Ok(()) => *faucetd.synced.lock().await = true, Ok(()) => *faucetd.synced.lock().await = true,

View File

@@ -441,6 +441,10 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
// Spawn configured networks // Spawn configured networks
let mut networks = vec![]; let mut networks = vec![];
for (name, info) in &configured_nets { for (name, info) in &configured_nets {
// TODO: Here we could actually differentiate between network versions
// e.g. p2p_v3, p2p_v4, etc. Therefore we can spawn multiple networks
// and they would all be version-checked, so we avoid mismatches when
// seeding peers.
match spawn_net( match spawn_net(
name.to_string(), name.to_string(),
info, info,

View File

@@ -28,7 +28,7 @@ use super::{
constants::{BLOCK_MAGIC_BYTES, BLOCK_VERSION}, constants::{BLOCK_MAGIC_BYTES, BLOCK_VERSION},
LeadInfo, LeadInfo,
}; };
use crate::{net, tx::Transaction, util::time::Timestamp}; use crate::{impl_p2p_message, net::Message, tx::Transaction, util::time::Timestamp};
/// This struct represents a tuple of the form (version, previous, epoch, slot, timestamp, merkle_root). /// This struct represents a tuple of the form (version, previous, epoch, slot, timestamp, merkle_root).
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
@@ -100,11 +100,7 @@ pub struct Block {
pub lead_info: LeadInfo, pub lead_info: LeadInfo,
} }
impl net::Message for Block { impl_p2p_message!(Block, "block");
fn name() -> &'static str {
"block"
}
}
impl Block { impl Block {
pub fn new( pub fn new(
@@ -146,11 +142,7 @@ pub struct BlockOrder {
pub block: blake3::Hash, pub block: blake3::Hash,
} }
impl net::Message for BlockOrder { impl_p2p_message!(BlockOrder, "blockorder");
fn name() -> &'static str {
"blockorder"
}
}
/// Structure representing full block data. /// Structure representing full block data.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
@@ -172,11 +164,7 @@ impl Default for BlockInfo {
} }
} }
impl net::Message for BlockInfo { impl_p2p_message!(BlockInfo, "blockinfo");
fn name() -> &'static str {
"blockinfo"
}
}
impl BlockInfo { impl BlockInfo {
pub fn new(header: Header, txs: Vec<Transaction>, lead_info: LeadInfo) -> Self { pub fn new(header: Header, txs: Vec<Transaction>, lead_info: LeadInfo) -> Self {
@@ -210,11 +198,7 @@ pub struct BlockResponse {
pub blocks: Vec<BlockInfo>, pub blocks: Vec<BlockInfo>,
} }
impl net::Message for BlockResponse { impl_p2p_message!(BlockResponse, "blockresponse");
fn name() -> &'static str {
"blockresponse"
}
}
/// This struct represents a block proposal, used for consensus. /// This struct represents a block proposal, used for consensus.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
@@ -260,11 +244,7 @@ impl fmt::Display for BlockProposal {
} }
} }
impl net::Message for BlockProposal { impl_p2p_message!(BlockProposal, "proposal");
fn name() -> &'static str {
"proposal"
}
}
impl From<BlockProposal> for BlockInfo { impl From<BlockProposal> for BlockInfo {
fn from(block: BlockProposal) -> BlockInfo { fn from(block: BlockProposal) -> BlockInfo {

View File

@@ -46,25 +46,22 @@ impl ProtocolProposal {
p2p: P2pPtr, p2p: P2pPtr,
) -> Result<ProtocolBasePtr> { ) -> Result<ProtocolBasePtr> {
debug!(target: "consensus::protocol_proposal::init()", "Adding ProtocolProposal to the protocol registry"); debug!(target: "consensus::protocol_proposal::init()", "Adding ProtocolProposal to the protocol registry");
let msg_subsystem = channel.get_message_subsystem(); let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::<BlockProposal>().await; msg_subsystem.add_dispatch::<BlockProposal>().await;
let proposal_sub = channel.subscribe_msg::<BlockProposal>().await?; let proposal_sub = channel.subscribe_msg::<BlockProposal>().await?;
let channel_address = channel.address();
Ok(Arc::new(Self { Ok(Arc::new(Self {
proposal_sub, proposal_sub,
jobsman: ProtocolJobsManager::new("ProposalProtocol", channel), jobsman: ProtocolJobsManager::new("ProposalProtocol", channel.clone()),
state, state,
p2p, p2p,
channel_address, channel_address: channel.address().clone(),
})) }))
} }
async fn handle_receive_proposal(self: Arc<Self>) -> Result<()> { async fn handle_receive_proposal(self: Arc<Self>) -> Result<()> {
debug!(target: "consensus::protocol_proposal::handle_receive_proposal()", "START"); debug!(target: "consensus::protocol_proposal::handle_receive_proposal()", "START");
let exclude_list = vec![self.channel_address.clone()]; let exclude_list = vec![self.channel_address.clone()];
loop { loop {
let proposal = match self.proposal_sub.receive().await { let proposal = match self.proposal_sub.receive().await {
@@ -104,15 +101,7 @@ impl ProtocolProposal {
Ok(broadcast) => { Ok(broadcast) => {
if broadcast { if broadcast {
// Broadcast proposal to rest of nodes // Broadcast proposal to rest of nodes
if let Err(e) = self.p2p.broadcast_with_exclude(&proposal_copy, &exclude_list).await;
self.p2p.broadcast_with_exclude(proposal_copy, &exclude_list).await
{
error!(
target: "consensus::protocol_proposal::handle_receive_proposal()",
"proposal broadcast fail: {}",
e
);
};
} }
} }
Err(e) => { Err(e) => {

View File

@@ -58,7 +58,7 @@ impl ProtocolSync {
p2p: P2pPtr, p2p: P2pPtr,
consensus_mode: bool, consensus_mode: bool,
) -> Result<ProtocolBasePtr> { ) -> Result<ProtocolBasePtr> {
let msg_subsystem = channel.get_message_subsystem(); let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::<BlockOrder>().await; msg_subsystem.add_dispatch::<BlockOrder>().await;
msg_subsystem.add_dispatch::<SlotRequest>().await; msg_subsystem.add_dispatch::<SlotRequest>().await;
msg_subsystem.add_dispatch::<BlockInfo>().await; msg_subsystem.add_dispatch::<BlockInfo>().await;
@@ -129,7 +129,7 @@ impl ProtocolSync {
let blocks = vec![BlockInfo::default()]; let blocks = vec![BlockInfo::default()];
let response = BlockResponse { blocks }; let response = BlockResponse { blocks };
if let Err(e) = self.channel.send(response).await { if let Err(e) = self.channel.send(&response).await {
error!( error!(
target: "consensus::protocol_sync::handle_receive_request()", target: "consensus::protocol_sync::handle_receive_request()",
"channel send fail: {}", "channel send fail: {}",
@@ -203,15 +203,7 @@ impl ProtocolSync {
target: "consensus::protocol_sync::handle_receive_block()", target: "consensus::protocol_sync::handle_receive_block()",
"block processed successfully, broadcasting..." "block processed successfully, broadcasting..."
); );
if let Err(e) = self.p2p.broadcast_with_exclude(&info_copy, &exclude_list).await;
self.p2p.broadcast_with_exclude(info_copy, &exclude_list).await
{
error!(
target: "consensus::protocol_sync::handle_receive_block()",
"p2p broadcast fail: {}",
e
);
};
} }
} }
Err(e) => { Err(e) => {
@@ -270,7 +262,7 @@ impl ProtocolSync {
); );
let response = SlotResponse { slots }; let response = SlotResponse { slots };
if let Err(e) = self.channel.send(response).await { if let Err(e) = self.channel.send(&response).await {
error!( error!(
target: "consensus::protocol_sync::handle_receive_slot_request()", target: "consensus::protocol_sync::handle_receive_slot_request()",
"channel send fail: {}", "channel send fail: {}",
@@ -285,7 +277,7 @@ impl ProtocolSync {
target: "consensus::protocol_sync::handle_receive_slot()", target: "consensus::protocol_sync::handle_receive_slot()",
"START" "START"
); );
let exclude_list = vec![self.channel.address()]; let exclude_list = vec![self.channel.address().clone()];
loop { loop {
let slot = match self.slots_sub.receive().await { let slot = match self.slots_sub.receive().await {
Ok(v) => v, Ok(v) => v,
@@ -346,15 +338,7 @@ impl ProtocolSync {
target: "consensus::protocol_sync::handle_receive_slot()", target: "consensus::protocol_sync::handle_receive_slot()",
"slot processed successfully, broadcasting..." "slot processed successfully, broadcasting..."
); );
if let Err(e) = self.p2p.broadcast_with_exclude(&slot_copy, &exclude_list).await;
self.p2p.broadcast_with_exclude(slot_copy, &exclude_list).await
{
error!(
target: "consensus::protocol_sync::handle_receive_slot()",
"p2p broadcast fail: {}",
e
);
};
} }
} }
Err(e) => { Err(e) => {

View File

@@ -47,7 +47,7 @@ impl ProtocolSyncConsensus {
state: ValidatorStatePtr, state: ValidatorStatePtr,
_p2p: P2pPtr, _p2p: P2pPtr,
) -> Result<ProtocolBasePtr> { ) -> Result<ProtocolBasePtr> {
let msg_subsystem = channel.get_message_subsystem(); let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::<ConsensusRequest>().await; msg_subsystem.add_dispatch::<ConsensusRequest>().await;
msg_subsystem.add_dispatch::<ConsensusSyncRequest>().await; msg_subsystem.add_dispatch::<ConsensusSyncRequest>().await;
@@ -128,7 +128,7 @@ impl ProtocolSyncConsensus {
err_history, err_history,
nullifiers, nullifiers,
}; };
if let Err(e) = self.channel.send(response).await { if let Err(e) = self.channel.send(&response).await {
error!( error!(
target: "consensus::protocol_sync_consensus::handle_receive_request()", target: "consensus::protocol_sync_consensus::handle_receive_request()",
"channel send fail: {}", "channel send fail: {}",
@@ -168,7 +168,7 @@ impl ProtocolSyncConsensus {
let proposing = lock.consensus.proposing; let proposing = lock.consensus.proposing;
let is_empty = lock.consensus.slots_is_empty(); let is_empty = lock.consensus.slots_is_empty();
let response = ConsensusSyncResponse { bootstrap_slot, proposing, is_empty }; let response = ConsensusSyncResponse { bootstrap_slot, proposing, is_empty };
if let Err(e) = self.channel.send(response).await { if let Err(e) = self.channel.send(&response).await {
error!( error!(
target: "consensus::protocol_sync_consensus::handle_receive_sync_request()", target: "consensus::protocol_sync_consensus::handle_receive_sync_request()",
"channel send fail: {}", "channel send fail: {}",

View File

@@ -18,15 +18,15 @@
use async_std::sync::Arc; use async_std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use log::{debug, error}; use log::debug;
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use crate::{ use crate::{
consensus::ValidatorStatePtr, consensus::ValidatorStatePtr,
net, impl_p2p_message,
net::{ net::{
ChannelPtr, MessageSubscription, P2pPtr, ProtocolBase, ProtocolBasePtr, ChannelPtr, Message, MessageSubscription, P2pPtr, ProtocolBase, ProtocolBasePtr,
ProtocolJobsManager, ProtocolJobsManagerPtr, ProtocolJobsManager, ProtocolJobsManagerPtr,
}, },
tx::Transaction, tx::Transaction,
@@ -41,11 +41,7 @@ pub struct ProtocolTx {
channel_address: Url, channel_address: Url,
} }
impl net::Message for Transaction { impl_p2p_message!(Transaction, "tx");
fn name() -> &'static str {
"tx"
}
}
impl ProtocolTx { impl ProtocolTx {
pub async fn init( pub async fn init(
@@ -57,18 +53,17 @@ impl ProtocolTx {
target: "consensus::protocol_tx::init()", target: "consensus::protocol_tx::init()",
"Adding ProtocolTx to the protocol registry" "Adding ProtocolTx to the protocol registry"
); );
let msg_subsystem = channel.get_message_subsystem(); let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::<Transaction>().await; msg_subsystem.add_dispatch::<Transaction>().await;
let tx_sub = channel.subscribe_msg::<Transaction>().await?; let tx_sub = channel.subscribe_msg::<Transaction>().await?;
let channel_address = channel.address();
Ok(Arc::new(Self { Ok(Arc::new(Self {
tx_sub, tx_sub,
jobsman: ProtocolJobsManager::new("TxProtocol", channel), jobsman: ProtocolJobsManager::new("TxProtocol", channel.clone()),
state, state,
p2p, p2p,
channel_address, channel_address: channel.address().clone(),
})) }))
} }
@@ -104,13 +99,7 @@ impl ProtocolTx {
// Nodes use unconfirmed_txs vector as seen_txs pool. // Nodes use unconfirmed_txs vector as seen_txs pool.
if self.state.write().await.append_tx(tx_copy.clone()).await { if self.state.write().await.append_tx(tx_copy.clone()).await {
if let Err(e) = self.p2p.broadcast_with_exclude(tx_copy, &exclude_list).await { self.p2p.broadcast_with_exclude(&tx_copy, &exclude_list).await;
error!(
target: "consensus::protocol_tx::handle_receive_tx()",
"p2p broadcast fail: {}",
e
);
};
} }
} }
} }

View File

@@ -33,7 +33,8 @@ use super::{
}; };
use crate::{ use crate::{
blockchain::Blockchain, blockchain::Blockchain,
net, impl_p2p_message,
net::Message,
tx::Transaction, tx::Transaction,
util::time::{TimeKeeper, Timestamp}, util::time::{TimeKeeper, Timestamp},
wallet::WalletPtr, wallet::WalletPtr,
@@ -631,12 +632,7 @@ impl ConsensusState {
/// Auxiliary structure used for consensus syncing. /// Auxiliary structure used for consensus syncing.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct ConsensusRequest {} pub struct ConsensusRequest {}
impl_p2p_message!(ConsensusRequest, "consensusrequest");
impl net::Message for ConsensusRequest {
fn name() -> &'static str {
"consensusrequest"
}
}
/// Auxiliary structure used for consensus syncing. /// Auxiliary structure used for consensus syncing.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
@@ -661,21 +657,13 @@ pub struct ConsensusResponse {
pub nullifiers: Vec<pallas::Base>, pub nullifiers: Vec<pallas::Base>,
} }
impl net::Message for ConsensusResponse { impl_p2p_message!(ConsensusResponse, "consensusresponse");
fn name() -> &'static str {
"consensusresponse"
}
}
/// Auxiliary structure used for consensus syncing. /// Auxiliary structure used for consensus syncing.
#[derive(Debug, SerialEncodable, SerialDecodable)] #[derive(Debug, SerialEncodable, SerialDecodable)]
pub struct ConsensusSyncRequest {} pub struct ConsensusSyncRequest {}
impl net::Message for ConsensusSyncRequest { impl_p2p_message!(ConsensusSyncRequest, "consensussyncrequest");
fn name() -> &'static str {
"consensussyncrequest"
}
}
/// Auxiliary structure used for consensus syncing. /// Auxiliary structure used for consensus syncing.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
@@ -688,17 +676,55 @@ pub struct ConsensusSyncResponse {
pub is_empty: bool, pub is_empty: bool,
} }
impl net::Message for ConsensusSyncResponse { impl_p2p_message!(ConsensusSyncResponse, "consensussyncresponse");
fn name() -> &'static str { impl_p2p_message!(Slot, "slot");
"consensussyncresponse"
/// Auxiliary structure used to keep track of slot validation parameters.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct SlotCheckpoint {
/// Slot UID
pub slot: u64,
/// Previous slot eta
pub previous_eta: pallas::Base,
/// Previous slot forks last proposal/block hashes,
/// as observed by the validator
pub fork_hashes: Vec<blake3::Hash>,
/// Previous slot second to last proposal/block hashes,
/// as observed by the validator
pub fork_previous_hashes: Vec<blake3::Hash>,
/// Slot sigma1
pub sigma1: pallas::Base,
/// Slot sigma2
pub sigma2: pallas::Base,
}
impl SlotCheckpoint {
pub fn new(
slot: u64,
previous_eta: pallas::Base,
fork_hashes: Vec<blake3::Hash>,
fork_previous_hashes: Vec<blake3::Hash>,
sigma1: pallas::Base,
sigma2: pallas::Base,
) -> Self {
Self { slot, previous_eta, fork_hashes, fork_previous_hashes, sigma1, sigma2 }
}
/// Generate the genesis slot checkpoint.
pub fn genesis_slot_checkpoint(genesis_block: blake3::Hash) -> Self {
let previous_eta = pallas::Base::ZERO;
let fork_hashes = vec![];
// Since genesis block has no previous,
// we will use its own hash as its previous.
let fork_previous_hashes = vec![genesis_block];
let sigma1 = pallas::Base::ZERO;
let sigma2 = pallas::Base::ZERO;
Self::new(0, previous_eta, fork_hashes, fork_previous_hashes, sigma1, sigma2)
} }
} }
impl net::Message for Slot { impl_p2p_message!(SlotCheckpoint, "slotcheckpoint");
fn name() -> &'static str {
"slot"
}
}
/// Auxiliary structure used for slots syncing /// Auxiliary structure used for slots syncing
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
@@ -707,11 +733,7 @@ pub struct SlotRequest {
pub slot: u64, pub slot: u64,
} }
impl net::Message for SlotRequest { impl_p2p_message!(SlotRequest, "slotrequest");
fn name() -> &'static str {
"slotrequest"
}
}
/// Auxiliary structure used for slots syncing /// Auxiliary structure used for slots syncing
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
@@ -720,11 +742,7 @@ pub struct SlotResponse {
pub slots: Vec<Slot>, pub slots: Vec<Slot>,
} }
impl net::Message for SlotResponse { impl_p2p_message!(SlotResponse, "slotresponse");
fn name() -> &'static str {
"slotresponse"
}
}
/// Auxiliary structure used to keep track of consensus state checkpoints. /// Auxiliary structure used to keep track of consensus state checkpoints.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@@ -30,9 +30,9 @@ use log::{debug, info, warn};
pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Result<()> { pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Result<()> {
info!(target: "consensus::block_sync", "Starting blockchain sync..."); info!(target: "consensus::block_sync", "Starting blockchain sync...");
// Getting a random connected channel to ask from peers // Getting a random connected channel to ask from peers
match p2p.clone().random_channel().await { match p2p.random_channel().await {
Some(channel) => { Some(channel) => {
let msg_subsystem = channel.get_message_subsystem(); let msg_subsystem = channel.message_subsystem();
// Communication setup for slots // Communication setup for slots
msg_subsystem.add_dispatch::<SlotResponse>().await; msg_subsystem.add_dispatch::<SlotResponse>().await;
@@ -54,7 +54,7 @@ pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Resu
loop { loop {
// Node creates a `SlotRequest` and sends it // Node creates a `SlotRequest` and sends it
let request = SlotRequest { slot: last.id }; let request = SlotRequest { slot: last.id };
channel.send(request).await?; channel.send(&request).await?;
// Node stores response data. // Node stores response data.
let resp = slot_response_sub.receive().await?; let resp = slot_response_sub.receive().await?;
@@ -88,7 +88,7 @@ pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Resu
loop { loop {
// Node creates a `BlockOrder` and sends it // Node creates a `BlockOrder` and sends it
let order = BlockOrder { slot: last.0, block: last.1 }; let order = BlockOrder { slot: last.0, block: last.1 };
channel.send(order).await?; channel.send(&order).await?;
// Node stores response data. // Node stores response data.
let _resp = block_response_sub.receive().await?; let _resp = block_response_sub.receive().await?;

View File

@@ -52,12 +52,13 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
let mut peer = None; let mut peer = None;
for channel in values { for channel in values {
// Communication setup // Communication setup
let msg_subsystem = channel.get_message_subsystem(); let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::<ConsensusSyncResponse>().await; msg_subsystem.add_dispatch::<ConsensusSyncResponse>().await;
let response_sub = channel.subscribe_msg::<ConsensusSyncResponse>().await?; let response_sub = channel.subscribe_msg::<ConsensusSyncResponse>().await?;
// Node creates a `ConsensusSyncRequest` and sends it // Node creates a `ConsensusSyncRequest` and sends it
let request = ConsensusSyncRequest {}; let request = ConsensusSyncRequest {};
channel.send(request).await?; channel.send(&request).await?;
// Node checks response // Node checks response
let response = response_sub.receive().await?; let response = response_sub.receive().await?;
if response.bootstrap_slot == current_slot { if response.bootstrap_slot == current_slot {
@@ -103,11 +104,11 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
// This ensures that the received state always consists of 1 fork with one proposal. // This ensures that the received state always consists of 1 fork with one proposal.
info!(target: "consensus::consensus_sync", "Finalization signal received, requesting consensus state..."); info!(target: "consensus::consensus_sync", "Finalization signal received, requesting consensus state...");
// Communication setup // Communication setup
let msg_subsystem = peer.get_message_subsystem(); let msg_subsystem = peer.message_subsystem();
msg_subsystem.add_dispatch::<ConsensusResponse>().await; msg_subsystem.add_dispatch::<ConsensusResponse>().await;
let response_sub = peer.subscribe_msg::<ConsensusResponse>().await?; let response_sub = peer.subscribe_msg::<ConsensusResponse>().await?;
// Node creates a `ConsensusRequest` and sends it // Node creates a `ConsensusRequest` and sends it
peer.send(ConsensusRequest {}).await?; peer.send(&ConsensusRequest {}).await?;
// Node verifies response came from a participating node. // Node verifies response came from a participating node.
// Extra validations can be added here. // Extra validations can be added here.
@@ -117,7 +118,7 @@ pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Resul
if !response.forks.is_empty() { if !response.forks.is_empty() {
warn!(target: "consensus::consensus_sync", "Peer has not finished finalization, retrying..."); warn!(target: "consensus::consensus_sync", "Peer has not finished finalization, retrying...");
sleep(1).await; sleep(1).await;
peer.send(ConsensusRequest {}).await?; peer.send(&ConsensusRequest {}).await?;
response = response_sub.receive().await?; response = response_sub.receive().await?;
continue continue
} }

View File

@@ -258,14 +258,7 @@ async fn propose_period(consensus_p2p: P2pPtr, state: ValidatorStatePtr) -> bool
// will always be true, since the node is able to produce proposals // will always be true, since the node is able to produce proposals
info!(target: "consensus::proposal", "consensus: Block proposal saved successfully"); info!(target: "consensus::proposal", "consensus: Block proposal saved successfully");
// Broadcast proposal to other consensus nodes // Broadcast proposal to other consensus nodes
match consensus_p2p.broadcast(proposal).await { consensus_p2p.broadcast(&proposal).await;
Ok(()) => {
info!(target: "consensus::proposal", "consensus: Proposal broadcasted successfully")
}
Err(e) => {
error!(target: "consensus::proposal", "consensus: Failed broadcasting proposal: {}", e)
}
}
} }
Err(e) => { Err(e) => {
error!(target: "consensus::proposal", "consensus: Block proposal save failed: {}", e); error!(target: "consensus::proposal", "consensus: Block proposal save failed: {}", e);
@@ -311,21 +304,15 @@ async fn finalization_period(
// Broadcast finalized blocks info, if any: // Broadcast finalized blocks info, if any:
info!(target: "consensus::proposal", "consensus: Broadcasting finalized blocks"); info!(target: "consensus::proposal", "consensus: Broadcasting finalized blocks");
for info in to_broadcast_block { for info in to_broadcast_block {
match sync_p2p.broadcast(info).await { sync_p2p.broadcast(&info).await;
Ok(()) => info!(target: "consensus::proposal", "consensus: Broadcasted block"),
Err(e) => error!(target: "consensus::proposal", "consensus: Failed broadcasting block: {}", e),
}
} }
// Broadcast finalized slots, if any: // Broadcast finalized slots, if any:
info!(target: "consensus::proposal", "consensus: Broadcasting finalized slots"); info!(target: "consensus::proposal", "consensus: Broadcasting finalized slots");
for slot in to_broadcast_slots { for slot in to_broadcast_slots {
match sync_p2p.broadcast(slot).await { sync_p2p.broadcast(slot).await;
Ok(()) => info!(target: "consensus::proposal", "consensus: Broadcasted slot"), info!(target: "consensus::proposal", "consensus: Broadcasted slot");
Err(e) => { // TODO: You can give an error if you query P2P and check if there are any connected channels
error!(target: "consensus::proposal", "consensus: Failed broadcasting slot: {}", e)
}
}
} }
}) })
.detach(); .detach();
@@ -338,7 +325,6 @@ async fn finalization_period(
} }
} }
*/ */
// Verify node didn't skip next slot // Verify node didn't skip next slot
completed_slot != state.read().await.consensus.time_keeper.current_slot() completed_slot != state.read().await.consensus.time_keeper.current_slot()
} }

View File

@@ -30,7 +30,7 @@ darkfi = {path = "../../../", features = ["tx", "blockchain"]}
darkfi-money-contract = { path = "../money", features = ["client", "no-entrypoint"] } darkfi-money-contract = { path = "../money", features = ["client", "no-entrypoint"] }
simplelog = "0.12.1" simplelog = "0.12.1"
sled = "0.34.7" sled = "0.34.7"
sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]} #sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]}
# We need to disable random using "custom" which makes the crate a noop # We need to disable random using "custom" which makes the crate a noop
# so the wasm32-unknown-unknown target is enabled. # so the wasm32-unknown-unknown target is enabled.

View File

@@ -28,7 +28,7 @@ async-std = {version = "1.12.0", features = ["attributes"]}
darkfi = {path = "../../../", features = ["tx", "blockchain"]} darkfi = {path = "../../../", features = ["tx", "blockchain"]}
simplelog = "0.12.1" simplelog = "0.12.1"
sled = "0.34.7" sled = "0.34.7"
sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]} #sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]}
darkfi-contract-test-harness = {path = "../test-harness"} darkfi-contract-test-harness = {path = "../test-harness"}
# We need to disable random using "custom" which makes the crate a noop # We need to disable random using "custom" which makes the crate a noop

View File

@@ -21,7 +21,7 @@ use std::collections::{HashMap, HashSet};
use darkfi_serial::{serialize, SerialDecodable, SerialEncodable}; use darkfi_serial::{serialize, SerialDecodable, SerialEncodable};
use rand::Rng; use rand::Rng;
use crate::net; use crate::{impl_p2p_message, net::Message};
/// This struct represents a DHT key request /// This struct represents a DHT key request
#[derive(Debug, Clone, SerialDecodable, SerialEncodable)] #[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
@@ -45,12 +45,7 @@ impl KeyRequest {
Self { id, from, to, key } Self { id, from, to, key }
} }
} }
impl_p2p_message!(KeyRequest, "keyrequest");
impl net::Message for KeyRequest {
fn name() -> &'static str {
"keyrequest"
}
}
/// This struct represents a DHT key request response /// This struct represents a DHT key request response
#[derive(Debug, Clone, SerialDecodable, SerialEncodable)] #[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
@@ -76,12 +71,7 @@ impl KeyResponse {
Self { id, from, to, key, value } Self { id, from, to, key, value }
} }
} }
impl_p2p_message!(KeyResponse, "keyresponse");
impl net::Message for KeyResponse {
fn name() -> &'static str {
"keyresponse"
}
}
/// This struct represents a lookup map request /// This struct represents a lookup map request
#[derive(Debug, Clone, SerialDecodable, SerialEncodable)] #[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
@@ -105,12 +95,7 @@ impl LookupRequest {
Self { id, daemon, key, req_type } Self { id, daemon, key, req_type }
} }
} }
impl_p2p_message!(LookupRequest, "lookuprequest");
impl net::Message for LookupRequest {
fn name() -> &'static str {
"lookuprequest"
}
}
/// Auxiliary structure used for lookup map syncing. /// Auxiliary structure used for lookup map syncing.
#[derive(Debug, SerialEncodable, SerialDecodable)] #[derive(Debug, SerialEncodable, SerialDecodable)]
@@ -130,12 +115,7 @@ impl LookupMapRequest {
Self { id, daemon } Self { id, daemon }
} }
} }
impl_p2p_message!(LookupMapRequest, "lookupmaprequest");
impl net::Message for LookupMapRequest {
fn name() -> &'static str {
"lookupmaprequest"
}
}
/// Auxiliary structure used for consensus syncing. /// Auxiliary structure used for consensus syncing.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
@@ -155,9 +135,4 @@ impl LookupMapResponse {
Self { id, lookup } Self { id, lookup }
} }
} }
impl_p2p_message!(LookupMapResponse, "lookupmapresponse");
impl net::Message for LookupMapResponse {
fn name() -> &'static str {
"lookupmapresponse"
}
}

View File

@@ -130,10 +130,7 @@ impl Dht {
}; };
let request = LookupRequest::new(self.id, key, 0); let request = LookupRequest::new(self.id, key, 0);
if let Err(e) = self.p2p.broadcast(request).await { self.p2p.broadcast(&request).await;
error!(target: "dht", "Failed broadcasting request: {}", e);
return Err(e)
}
Ok(Some(key)) Ok(Some(key))
} }
@@ -145,7 +142,7 @@ impl Dht {
Some(_) => { Some(_) => {
debug!(target: "dht", "Key removed: {}", key); debug!(target: "dht", "Key removed: {}", key);
let request = LookupRequest::new(self.id, key, 1); let request = LookupRequest::new(self.id, key, 1);
if let Err(e) = self.p2p.broadcast(request).await { if let Err(e) = self.p2p.broadcast(&request).await {
error!(target: "dht", "Failed broadcasting request: {}", e); error!(target: "dht", "Failed broadcasting request: {}", e);
return Err(e) return Err(e)
} }
@@ -247,13 +244,13 @@ impl Dht {
// Node iterates the channel peers to ask for their lookup map // Node iterates the channel peers to ask for their lookup map
for channel in values { for channel in values {
// Communication setup // Communication setup
let msg_subsystem = channel.get_message_subsystem(); let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::<LookupMapResponse>().await; msg_subsystem.add_dispatch::<LookupMapResponse>().await;
let response_sub = channel.subscribe_msg::<LookupMapResponse>().await?; let response_sub = channel.subscribe_msg::<LookupMapResponse>().await?;
// Node creates a `LookupMapRequest` and sends it // Node creates a `LookupMapRequest` and sends it
let order = LookupMapRequest::new(self.id); let order = LookupMapRequest::new(self.id);
channel.send(order).await?; channel.send(&order).await?;
// Node stores response data. // Node stores response data.
let resp = response_sub.receive().await?; let resp = response_sub.receive().await?;
@@ -285,11 +282,7 @@ impl Dht {
pub async fn waiting_for_response(dht: DhtPtr) -> Result<Option<KeyResponse>> { pub async fn waiting_for_response(dht: DhtPtr) -> Result<Option<KeyResponse>> {
let (p2p_recv_channel, stop_signal, timeout) = { let (p2p_recv_channel, stop_signal, timeout) = {
let _dht = dht.read().await; let _dht = dht.read().await;
( (_dht.p2p_recv_channel.clone(), _dht.stop_signal.clone(), 666)
_dht.p2p_recv_channel.clone(),
_dht.stop_signal.clone(),
_dht.p2p.settings().connect_timeout_seconds as u64,
)
}; };
let ex = Arc::new(Executor::new()); let ex = Arc::new(Executor::new());
let (timeout_s, timeout_r) = smol::channel::unbounded::<()>(); let (timeout_s, timeout_r) = smol::channel::unbounded::<()>();

View File

@@ -81,7 +81,7 @@ impl Protocol {
async fn handle_receive_request(self: Arc<Self>) -> Result<()> { async fn handle_receive_request(self: Arc<Self>) -> Result<()> {
debug!(target: "dht::protocol", "Protocol::handle_receive_request() [START]"); debug!(target: "dht::protocol", "Protocol::handle_receive_request() [START]");
let exclude_list = vec![self.channel.address()]; let exclude_list = vec![self.channel.address().clone()];
loop { loop {
let req = match self.req_sub.receive().await { let req = match self.req_sub.receive().await {
Ok(v) => v, Ok(v) => v,
@@ -109,12 +109,7 @@ impl Protocol {
let daemon = self.dht.read().await.id; let daemon = self.dht.read().await.id;
if daemon != req_copy.to { if daemon != req_copy.to {
if let Err(e) = self.p2p.broadcast_with_exclude(&req_copy, &exclude_list).await;
self.p2p.broadcast_with_exclude(req_copy.clone(), &exclude_list).await
{
error!(target: "dht::protocol", "Protocol::handle_receive_response(): p2p broadcast fail: {}", e);
};
continue
} }
match self.dht.read().await.map.get(&req_copy.key) { match self.dht.read().await.map.get(&req_copy.key) {
@@ -122,7 +117,7 @@ impl Protocol {
let response = let response =
KeyResponse::new(daemon, req_copy.from, req_copy.key, value.clone()); KeyResponse::new(daemon, req_copy.from, req_copy.key, value.clone());
debug!(target: "dht::protocol", "Protocol::handle_receive_request(): sending response: {:?}", response); debug!(target: "dht::protocol", "Protocol::handle_receive_request(): sending response: {:?}", response);
if let Err(e) = self.channel.send(response).await { if let Err(e) = self.channel.send(&response).await {
error!(target: "dht::protocol", "Protocol::handle_receive_request(): p2p broadcast of response failed: {}", e); error!(target: "dht::protocol", "Protocol::handle_receive_request(): p2p broadcast of response failed: {}", e);
}; };
} }
@@ -135,7 +130,7 @@ impl Protocol {
async fn handle_receive_response(self: Arc<Self>) -> Result<()> { async fn handle_receive_response(self: Arc<Self>) -> Result<()> {
debug!(target: "dht::protocol", "Protocol::handle_receive_response() [START]"); debug!(target: "dht::protocol", "Protocol::handle_receive_response() [START]");
let exclude_list = vec![self.channel.address()]; let exclude_list = vec![self.channel.address().clone()];
loop { loop {
let resp = match self.resp_sub.receive().await { let resp = match self.resp_sub.receive().await {
Ok(v) => v, Ok(v) => v,
@@ -162,12 +157,7 @@ impl Protocol {
} }
if self.dht.read().await.id != resp_copy.to { if self.dht.read().await.id != resp_copy.to {
if let Err(e) = self.p2p.broadcast_with_exclude(&resp_copy, &exclude_list).await;
self.p2p.broadcast_with_exclude(resp_copy.clone(), &exclude_list).await
{
error!(target: "dht::protocol", "Protocol::handle_receive_response(): p2p broadcast fail: {}", e);
};
continue
} }
self.notify_queue_sender.send(resp_copy.clone()).await?; self.notify_queue_sender.send(resp_copy.clone()).await?;
@@ -176,7 +166,7 @@ impl Protocol {
async fn handle_receive_lookup_request(self: Arc<Self>) -> Result<()> { async fn handle_receive_lookup_request(self: Arc<Self>) -> Result<()> {
debug!(target: "dht::protocol", "Protocol::handle_receive_lookup_request() [START]"); debug!(target: "dht::protocol", "Protocol::handle_receive_lookup_request() [START]");
let exclude_list = vec![self.channel.address()]; let exclude_list = vec![self.channel.address().clone()];
loop { loop {
let req = match self.lookup_sub.receive().await { let req = match self.lookup_sub.receive().await {
Ok(v) => v, Ok(v) => v,
@@ -217,9 +207,7 @@ impl Protocol {
continue continue
}; };
if let Err(e) = self.p2p.broadcast_with_exclude(req_copy, &exclude_list).await { self.p2p.broadcast_with_exclude(&req_copy, &exclude_list).await;
error!(target: "dht::protocol", "Protocol::handle_receive_lookup_request(): p2p broadcast fail: {}", e);
};
} }
} }
@@ -252,7 +240,7 @@ impl Protocol {
// Extra validations can be added here. // Extra validations can be added here.
let lookup = self.dht.read().await.lookup.clone(); let lookup = self.dht.read().await.lookup.clone();
let response = LookupMapResponse::new(lookup); let response = LookupMapResponse::new(lookup);
if let Err(e) = self.channel.send(response).await { if let Err(e) = self.channel.send(&response).await {
error!(target: "dht::protocol", "Protocol::handle_receive_lookup_map_request() channel send fail: {}", e); error!(target: "dht::protocol", "Protocol::handle_receive_lookup_map_request() channel send fail: {}", e);
}; };
} }

View File

@@ -84,7 +84,7 @@ where
/// Additionally, this change will be broadcasted to the P2P network. /// Additionally, this change will be broadcasted to the P2P network.
pub async fn insert(&mut self, k: K, v: V) -> Result<Option<V>> { pub async fn insert(&mut self, k: K, v: V) -> Result<Option<V>> {
let message = NetHashMapInsert { k: k.clone(), v: v.clone() }; let message = NetHashMapInsert { k: k.clone(), v: v.clone() };
self.p2p.broadcast(message).await?; self.p2p.broadcast(&message).await;
Ok(self.hashmap.insert(k, v)) Ok(self.hashmap.insert(k, v))
} }
@@ -101,7 +101,7 @@ where
Q: Hash + Eq + Send + Sync + Encodable + Decodable + 'static, Q: Hash + Eq + Send + Sync + Encodable + Decodable + 'static,
{ {
let message = NetHashMapRemove { k: k.clone() }; let message = NetHashMapRemove { k: k.clone() };
self.p2p.broadcast(message).await?; self.p2p.broadcast(&message).await;
Ok(self.hashmap.remove(&k)) Ok(self.hashmap.remove(&k))
} }
@@ -138,9 +138,7 @@ where
K: Encodable + Decodable + Send + Sync + 'static, K: Encodable + Decodable + Send + Sync + 'static,
V: Encodable + Decodable + Send + Sync + 'static, V: Encodable + Decodable + Send + Sync + 'static,
{ {
fn name() -> &'static str { const NAME: &'static str = "nethashmap_insert";
"nethashmap_insert"
}
} }
#[derive(Debug, Clone, SerialDecodable, SerialEncodable)] #[derive(Debug, Clone, SerialDecodable, SerialEncodable)]
@@ -152,7 +150,5 @@ impl<K> net::Message for NetHashMapRemove<K>
where where
K: Encodable + Decodable + Send + Sync + 'static, K: Encodable + Decodable + Send + Sync + 'static,
{ {
fn name() -> &'static str { const NAME: &'static str = "nethashmap_remove";
"nethashmap_remove"
}
} }

View File

@@ -100,6 +100,12 @@ pub enum Error {
// ====================== // ======================
// Network-related errors // Network-related errors
// ====================== // ======================
#[error("Invalid Dialer scheme")]
InvalidDialerScheme,
#[error("Invalid Listener scheme")]
InvalidListenerScheme,
#[error("Unsupported network transport: {0}")] #[error("Unsupported network transport: {0}")]
UnsupportedTransport(String), UnsupportedTransport(String),
@@ -136,6 +142,10 @@ pub enum Error {
#[error("Network operation failed")] #[error("Network operation failed")]
NetworkOperationFailed, NetworkOperationFailed,
#[cfg(feature = "arti-client")]
#[error(transparent)]
ArtiError(#[from] arti_client::Error),
#[error("Malformed packet")] #[error("Malformed packet")]
MalformedPacket, MalformedPacket,
@@ -189,11 +199,11 @@ pub enum Error {
#[error("Invalid DarkFi address")] #[error("Invalid DarkFi address")]
InvalidAddress, InvalidAddress,
#[cfg(feature = "futures-rustls")] #[cfg(feature = "async-rustls")]
#[error(transparent)] #[error(transparent)]
RustlsError(#[from] futures_rustls::rustls::Error), RustlsError(#[from] async_rustls::rustls::Error),
#[cfg(feature = "futures-rustls")] #[cfg(feature = "async-rustls")]
#[error("Invalid DNS Name {0}")] #[error("Invalid DNS Name {0}")]
RustlsInvalidDns(String), RustlsInvalidDns(String),
@@ -279,9 +289,9 @@ pub enum Error {
// =============== // ===============
// Database errors // Database errors
// =============== // ===============
#[cfg(feature = "sqlx")] #[cfg(feature = "rusqlite")]
#[error("Sqlx error: {0}")] #[error("rusqlite error: {0}")]
SqlxError(String), RusqliteError(String),
#[cfg(feature = "sled")] #[cfg(feature = "sled")]
#[error(transparent)] #[error(transparent)]
@@ -589,10 +599,10 @@ impl From<log::SetLoggerError> for Error {
} }
} }
#[cfg(feature = "sqlx")] #[cfg(feature = "rusqlite")]
impl From<sqlx::error::Error> for Error { impl From<rusqlite::Error> for Error {
fn from(err: sqlx::error::Error) -> Self { fn from(err: rusqlite::Error) -> Self {
Self::SqlxError(err.to_string()) Self::RusqliteError(err.to_string())
} }
} }
@@ -619,9 +629,9 @@ impl From<async_tungstenite::tungstenite::Error> for Error {
} }
} }
#[cfg(feature = "futures-rustls")] #[cfg(feature = "async-rustls")]
impl From<futures_rustls::rustls::client::InvalidDnsNameError> for Error { impl From<async_rustls::rustls::client::InvalidDnsNameError> for Error {
fn from(err: futures_rustls::rustls::client::InvalidDnsNameError) -> Self { fn from(err: async_rustls::rustls::client::InvalidDnsNameError) -> Self {
Self::RustlsInvalidDns(err.to_string()) Self::RustlsInvalidDns(err.to_string())
} }
} }

View File

@@ -26,7 +26,8 @@ use log::debug;
use super::EventMsg; use super::EventMsg;
use crate::{ use crate::{
event_graph::model::{Event, EventId, ModelPtr}, event_graph::model::{Event, EventId, ModelPtr},
net, impl_p2p_message, net,
net::Message,
util::{async_util::sleep, ringbuffer::RingBuffer}, util::{async_util::sleep, ringbuffer::RingBuffer},
Result, Result,
}; };
@@ -42,16 +43,19 @@ pub struct InvItem {
pub struct Inv { pub struct Inv {
pub invs: Vec<InvItem>, pub invs: Vec<InvItem>,
} }
impl_p2p_message!(Inv, "inv");
#[derive(SerialDecodable, SerialEncodable, Clone, Debug)] #[derive(SerialDecodable, SerialEncodable, Clone, Debug)]
struct SyncEvent { struct SyncEvent {
leaves: Vec<EventId>, leaves: Vec<EventId>,
} }
impl_p2p_message!(SyncEvent, "syncevent");
#[derive(SerialDecodable, SerialEncodable, Clone, Debug)] #[derive(SerialDecodable, SerialEncodable, Clone, Debug)]
struct GetData { struct GetData {
events: Vec<EventId>, events: Vec<EventId>,
} }
impl_p2p_message!(GetData, "getdata");
pub type SeenPtr<T> = Arc<Seen<T>>; pub type SeenPtr<T> = Arc<Seen<T>>;
@@ -101,7 +105,7 @@ where
seen_event: SeenPtr<EventId>, seen_event: SeenPtr<EventId>,
seen_inv: SeenPtr<EventId>, seen_inv: SeenPtr<EventId>,
) -> net::ProtocolBasePtr { ) -> net::ProtocolBasePtr {
let message_subsytem = channel.get_message_subsystem(); let message_subsytem = channel.message_subsystem();
message_subsytem.add_dispatch::<Event<T>>().await; message_subsytem.add_dispatch::<Event<T>>().await;
message_subsytem.add_dispatch::<Inv>().await; message_subsytem.add_dispatch::<Inv>().await;
message_subsytem.add_dispatch::<GetData>().await; message_subsytem.add_dispatch::<GetData>().await;
@@ -137,7 +141,7 @@ where
async fn handle_receive_event(self: Arc<Self>) -> Result<()> { async fn handle_receive_event(self: Arc<Self>) -> Result<()> {
debug!(target: "event_graph", "ProtocolEvent::handle_receive_event() [START]"); debug!(target: "event_graph", "ProtocolEvent::handle_receive_event() [START]");
let exclude_list = vec![self.channel.address()]; let exclude_list = vec![self.channel.address().clone()];
loop { loop {
let event = self.event_sub.receive().await?; let event = self.event_sub.receive().await?;
let event = (*event).to_owned(); let event = (*event).to_owned();
@@ -152,13 +156,13 @@ where
self.send_inv(&event).await?; self.send_inv(&event).await?;
// Broadcast the msg // Broadcast the msg
self.p2p.broadcast_with_exclude(event, &exclude_list).await?; self.p2p.broadcast_with_exclude(&event, &exclude_list).await;
} }
} }
async fn handle_receive_inv(self: Arc<Self>) -> Result<()> { async fn handle_receive_inv(self: Arc<Self>) -> Result<()> {
debug!(target: "event_graph", "ProtocolEvent::handle_receive_inv() [START]"); debug!(target: "event_graph", "ProtocolEvent::handle_receive_inv() [START]");
let exclude_list = vec![self.channel.address()]; let exclude_list = vec![self.channel.address().clone()];
loop { loop {
let inv = self.inv_sub.receive().await?; let inv = self.inv_sub.receive().await?;
let inv = (*inv).to_owned(); let inv = (*inv).to_owned();
@@ -176,7 +180,7 @@ where
// } // }
// Broadcast the inv msg // Broadcast the inv msg
self.p2p.broadcast_with_exclude(inv, &exclude_list).await?; self.p2p.broadcast_with_exclude(&inv, &exclude_list).await;
} }
} }
async fn handle_receive_getdata(self: Arc<Self>) -> Result<()> { async fn handle_receive_getdata(self: Arc<Self>) -> Result<()> {
@@ -188,7 +192,7 @@ where
for event_id in events { for event_id in events {
let model_event = self.model.lock().await.get_event(&event_id); let model_event = self.model.lock().await.get_event(&event_id);
if let Some(event) = model_event { if let Some(event) = model_event {
self.channel.send(event).await?; self.channel.send(&event).await?;
} }
} }
} }
@@ -214,7 +218,7 @@ where
let children = model.get_offspring(leaf); let children = model.get_offspring(leaf);
for child in children { for child in children {
self.channel.send(child).await?; self.channel.send(&child).await?;
} }
} }
} }
@@ -226,7 +230,7 @@ where
loop { loop {
sleep(6).await; sleep(6).await;
let leaves = self.model.lock().await.find_leaves(); let leaves = self.model.lock().await.find_leaves();
self.channel.send(SyncEvent { leaves }).await?; self.channel.send(&SyncEvent { leaves }).await?;
} }
} }
@@ -240,14 +244,14 @@ where
async fn send_inv(&self, event: &Event<T>) -> Result<()> { async fn send_inv(&self, event: &Event<T>) -> Result<()> {
debug!(target: "event_graph", "ProtocolEvent::send_inv()"); debug!(target: "event_graph", "ProtocolEvent::send_inv()");
self.p2p.broadcast(Inv { invs: vec![InvItem { hash: event.hash() }] }).await?; self.p2p.broadcast(&Inv { invs: vec![InvItem { hash: event.hash() }] }).await;
Ok(()) Ok(())
} }
async fn send_getdata(&self, events: Vec<EventId>) -> Result<()> { async fn send_getdata(&self, events: Vec<EventId>) -> Result<()> {
debug!(target: "event_graph", "ProtocolEvent::send_getdata()"); debug!(target: "event_graph", "ProtocolEvent::send_getdata()");
self.channel.send(GetData { events }).await?; self.channel.send(&GetData { events }).await?;
Ok(()) Ok(())
} }
} }
@@ -278,25 +282,5 @@ impl<T> net::Message for Event<T>
where where
T: Send + Sync + Decodable + Encodable + 'static, T: Send + Sync + Decodable + Encodable + 'static,
{ {
fn name() -> &'static str { const NAME: &'static str = "event";
"event"
}
}
impl net::Message for Inv {
fn name() -> &'static str {
"inv"
}
}
impl net::Message for SyncEvent {
fn name() -> &'static str {
"syncevent"
}
}
impl net::Message for GetData {
fn name() -> &'static str {
"getdata"
}
} }

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#![feature(ip)]
pub mod error; pub mod error;
pub use error::{ClientFailed, ClientResult, Error, Result}; pub use error::{ClientFailed, ClientResult, Error, Result};

View File

@@ -16,27 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{env, fs};
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, Mutex};
use log::{error, info}; use log::error;
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use super::{ use super::{
transport::{TcpTransport, TorTransport, Transport, TransportListener, TransportName}, channel::{Channel, ChannelPtr},
Channel, ChannelPtr, SessionWeakPtr, session::SessionWeakPtr,
transport::{Listener, PtListener},
}; };
use crate::{ use crate::{
net::transport::NymTransport,
system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr, Subscription}, system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr, Subscription},
Error, Result, Error, Result,
}; };
/// Atomic pointer to Acceptor class. /// Atomic pointer to Acceptor
pub type AcceptorPtr = Arc<Acceptor>; pub type AcceptorPtr = Arc<Acceptor>;
/// Create inbound socket connections. /// Create inbound socket connections
pub struct Acceptor { pub struct Acceptor {
channel_subscriber: SubscriberPtr<Result<ChannelPtr>>, channel_subscriber: SubscriberPtr<Result<ChannelPtr>>,
task: StoppableTaskPtr, task: StoppableTaskPtr,
@@ -45,105 +43,22 @@ pub struct Acceptor {
impl Acceptor { impl Acceptor {
/// Create new Acceptor object. /// Create new Acceptor object.
pub fn new(session: Mutex<Option<SessionWeakPtr>>) -> Arc<Self> { pub fn new(session: Mutex<Option<SessionWeakPtr>>) -> AcceptorPtr {
Arc::new(Self { Arc::new(Self {
channel_subscriber: Subscriber::new(), channel_subscriber: Subscriber::new(),
task: StoppableTask::new(), task: StoppableTask::new(),
session, session,
}) })
} }
/// Start accepting inbound socket connections. Creates a listener to start
/// listening on a local socket address. Then runs an accept loop in a new
/// thread, erroring if a connection problem occurs.
pub async fn start(
self: Arc<Self>,
accept_url: Url,
executor: Arc<Executor<'_>>,
) -> Result<()> {
let transport_name = TransportName::try_from(accept_url.clone())?;
macro_rules! accept { /// Start accepting inbound socket connections
($listener:expr, $transport:expr, $upgrade:expr) => {{ pub async fn start(self: Arc<Self>, endpoint: Url, ex: Arc<Executor<'_>>) -> Result<()> {
if let Err(err) = $listener { let listener = Listener::new(endpoint).await?.listen().await?;
error!(target: "net::acceptor", "Setup for {} failed: {}", accept_url, err); self.accept(listener, ex);
return Err(Error::BindFailed(accept_url.as_str().into()))
}
let listener = $listener?.await;
if let Err(err) = listener {
error!(target: "net::acceptor", "Bind listener to {} failed: {}", accept_url, err);
return Err(Error::BindFailed(accept_url.as_str().into()))
}
let listener = listener?;
match $upgrade {
None => {
self.accept(Box::new(listener), executor);
}
Some(u) if u == "tls" => {
let tls_listener = $transport.upgrade_listener(listener)?.await?;
self.accept(Box::new(tls_listener), executor);
}
Some(u) => return Err(Error::UnsupportedTransportUpgrade(u)),
}
}};
}
match transport_name {
TransportName::Tcp(upgrade) => {
let transport = TcpTransport::new(None, 1024);
let listener = transport.listen_on(accept_url.clone());
accept!(listener, transport, upgrade);
}
TransportName::Tor(upgrade) => {
let socks5_url = Url::parse(
&env::var("DARKFI_TOR_SOCKS5_URL")
.unwrap_or_else(|_| "socks5://127.0.0.1:9050".to_string()),
)?;
let torc_url = Url::parse(
&env::var("DARKFI_TOR_CONTROL_URL")
.unwrap_or_else(|_| "tcp://127.0.0.1:9051".to_string()),
)?;
let auth_cookie = env::var("DARKFI_TOR_COOKIE");
if auth_cookie.is_err() {
return Err(Error::TorError(
"Please set the env var DARKFI_TOR_COOKIE to the configured tor cookie file. \
For example: \
\'export DARKFI_TOR_COOKIE=\"/var/lib/tor/control_auth_cookie\"\'".to_string(),
));
}
let auth_cookie = auth_cookie.unwrap();
let auth_cookie = hex::encode(fs::read(auth_cookie).unwrap());
let transport = TorTransport::new(socks5_url, Some((torc_url, auth_cookie)))?;
// generate EHS pointing to local address
let hurl = transport.create_ehs(accept_url.clone())?;
info!(target: "net::acceptor", "EHS TOR: {}", hurl.to_string());
let listener = transport.clone().listen_on(accept_url.clone());
accept!(listener, transport, upgrade);
}
TransportName::Nym(upgrade) => {
let transport = NymTransport::new()?;
let listener = transport.clone().listen_on(accept_url.clone());
accept!(listener, transport, upgrade);
}
_ => unimplemented!(),
}
Ok(()) Ok(())
} }
/// Stop accepting inbound socket connections. /// Stop accepting inbound socket connections
pub async fn stop(&self) { pub async fn stop(&self) {
// Send stop signal // Send stop signal
self.task.stop().await; self.task.stop().await;
@@ -154,20 +69,19 @@ impl Acceptor {
self.channel_subscriber.clone().subscribe().await self.channel_subscriber.clone().subscribe().await
} }
/// Run the accept loop in a new thread and error if a connection problem /// Run the accept loop in a new thread and error if a connection problem occurs
/// occurs. fn accept(self: Arc<Self>, listener: Box<dyn PtListener>, ex: Arc<Executor<'_>>) {
fn accept(self: Arc<Self>, listener: Box<dyn TransportListener>, executor: Arc<Executor<'_>>) { let self_ = self.clone();
let self2 = self.clone();
self.task.clone().start( self.task.clone().start(
self.clone().run_accept_loop(listener), self.run_accept_loop(listener),
|result| self2.handle_stop(result), |result| self_.handle_stop(result),
Error::NetworkServiceStopped, Error::NetworkServiceStopped,
executor, ex,
); );
} }
/// Run the accept loop. /// Run the accept loop.
async fn run_accept_loop(self: Arc<Self>, listener: Box<dyn TransportListener>) -> Result<()> { async fn run_accept_loop(self: Arc<Self>, listener: Box<dyn PtListener>) -> Result<()> {
loop { loop {
match listener.next().await { match listener.next().await {
Ok((stream, url)) => { Ok((stream, url)) => {
@@ -175,23 +89,23 @@ impl Acceptor {
Channel::new(stream, url, self.session.lock().await.clone().unwrap()).await; Channel::new(stream, url, self.session.lock().await.clone().unwrap()).await;
self.channel_subscriber.notify(Ok(channel)).await; self.channel_subscriber.notify(Ok(channel)).await;
} }
Err(e) => { Err(e) => {
error!(target: "net::acceptor", "Error listening for new connection: {}", e); error!(
target: "net::acceptor::run_accept_loop()",
"[P2P] Acceptor failed listening: {}", e,
);
} }
} }
} }
} }
/// Handles network errors. Panics if error passes silently, otherwise /// Handles network errors. Panics if errors pass silently, otherwise broadcasts it
/// broadcasts the error. /// to all channel subscribers.
async fn handle_stop(self: Arc<Self>, result: Result<()>) { async fn handle_stop(self: Arc<Self>, result: Result<()>) {
match result { match result {
Ok(()) => panic!("Acceptor task should never complete without error status"), Ok(()) => panic!("Acceptor task should never complete without error status"),
Err(err) => { Err(err) => self.channel_subscriber.notify(Err(err)).await,
// Send this error to all channel subscribers
let result = Err(err);
self.channel_subscriber.notify(result).await;
}
} }
} }
} }

View File

@@ -17,21 +17,24 @@
*/ */
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, Mutex};
use darkfi_serial::serialize;
use futures::{ use futures::{
io::{ReadHalf, WriteHalf}, io::{ReadHalf, WriteHalf},
AsyncReadExt, AsyncReadExt,
}; };
use log::{debug, error, info}; use log::{debug, error, info};
use rand::Rng; use rand::{rngs::OsRng, Rng};
use serde_json::json; use serde_json::json;
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use super::{ use super::{
message, message,
message::Packet,
message_subscriber::{MessageSubscription, MessageSubsystem}, message_subscriber::{MessageSubscription, MessageSubsystem},
transport::TransportStream, p2p::{dnet, P2pPtr},
Session, SessionBitflag, SessionWeakPtr, session::{Session, SessionBitFlag, SessionWeakPtr},
transport::PtStream,
}; };
use crate::{ use crate::{
system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr, Subscription}, system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr, Subscription},
@@ -39,78 +42,85 @@ use crate::{
Error, Result, Error, Result,
}; };
/// Atomic pointer to async channel. /// Atomic pointer to async channel
pub type ChannelPtr = Arc<Channel>; pub type ChannelPtr = Arc<Channel>;
const SIZE_OF_BUFFER: usize = 65536; const RINGBUFFER_SIZE: usize = 512;
/// Channel debug info
struct ChannelInfo { struct ChannelInfo {
random_id: u32, random_id: u32,
remote_node_id: String, remote_node_id: String,
last_msg: String, log: Mutex<RingBuffer<(NanoTimestamp, String, String)>>,
last_status: String,
// Message log which is cleared on querying get_info
log: Option<Mutex<RingBuffer<(NanoTimestamp, String, String)>>>,
} }
impl ChannelInfo { impl ChannelInfo {
fn new(channel_log: bool) -> Self { fn new() -> Self {
let log = match channel_log {
true => Some(Mutex::new(RingBuffer::new(SIZE_OF_BUFFER))),
false => None,
};
Self { Self {
random_id: rand::thread_rng().gen(), random_id: OsRng.gen(),
remote_node_id: String::new(), remote_node_id: String::new(),
last_msg: String::new(), log: Mutex::new(RingBuffer::new(RINGBUFFER_SIZE)),
last_status: String::new(),
log,
} }
} }
// ANCHOR: get_info /// Get available debug info, resets the ringbuffer when called.
async fn get_info(&self) -> serde_json::Value { async fn get_info(&self) -> serde_json::Value {
let log = match &self.log { let mut lock = self.log.lock().await;
Some(l) => { let log = lock.clone();
let mut lock = l.lock().await; *lock = RingBuffer::new(RINGBUFFER_SIZE);
let ret = lock.clone(); drop(lock);
*lock = RingBuffer::new(SIZE_OF_BUFFER);
ret let (last_msg, last_status) = {
match log.back() {
Some((_, m, s)) => (m.clone(), s.clone()),
None => (String::new(), String::new()),
} }
None => RingBuffer::new(0),
}; };
json!({ json!({
"random_id": self.random_id, "random_id": self.random_id,
"remote_node_id": self.remote_node_id, "remote_node_id": self.remote_node_id,
"last_msg": self.last_msg, "last_msg": last_msg,
"last_status": self.last_status, "last_status": last_status,
"log": log, "log": log,
}) })
} }
// ANCHOR_END: get_info
} }
/// Async channel for communication between nodes. /// Async channel for communication between nodes.
pub struct Channel { pub struct Channel {
reader: Mutex<ReadHalf<Box<dyn TransportStream>>>, /// The reading half of the transport stream
writer: Mutex<WriteHalf<Box<dyn TransportStream>>>, reader: Mutex<ReadHalf<Box<dyn PtStream>>>,
/// The writing half of the transport stream
writer: Mutex<WriteHalf<Box<dyn PtStream>>>,
/// Socket address
address: Url, address: Url,
/// The message subsystem instance for this channel
message_subsystem: MessageSubsystem, message_subsystem: MessageSubsystem,
/// Subscriber listening for stop signal for closing this channel
stop_subscriber: SubscriberPtr<Error>, stop_subscriber: SubscriberPtr<Error>,
/// Task that is listening for the stop signal
receive_task: StoppableTaskPtr, receive_task: StoppableTaskPtr,
/// A boolean marking if this channel is stopped
stopped: Mutex<bool>, stopped: Mutex<bool>,
info: Mutex<ChannelInfo>, /// Weak pointer to respective session
session: SessionWeakPtr, session: SessionWeakPtr,
/// Channel debug info
info: Mutex<Option<ChannelInfo>>,
}
impl std::fmt::Debug for Channel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.address)
}
} }
impl Channel { impl Channel {
/// Sets up a new channel. Creates a reader and writer TCP stream and /// Sets up a new channel. Creates a reader and writer [`PtStream`] and
/// summons the message subscriber subsystem. Performs a network /// summons the message subscriber subsystem. Performs a network handshake
/// handshake on the subsystem dispatchers. /// on the subsystem dispatchers.
pub async fn new( pub async fn new(
stream: Box<dyn TransportStream>, stream: Box<dyn PtStream>,
address: Url, address: Url,
session: SessionWeakPtr, session: SessionWeakPtr,
) -> Arc<Self> { ) -> Arc<Self> {
@@ -121,7 +131,11 @@ impl Channel {
let message_subsystem = MessageSubsystem::new(); let message_subsystem = MessageSubsystem::new();
Self::setup_dispatchers(&message_subsystem).await; Self::setup_dispatchers(&message_subsystem).await;
let channel_log = session.upgrade().unwrap().p2p().settings().channel_log; let info = if *session.upgrade().unwrap().p2p().dnet_enabled.lock().await {
Mutex::new(Some(ChannelInfo::new()))
} else {
Mutex::new(None)
};
Arc::new(Self { Arc::new(Self {
reader, reader,
@@ -131,259 +145,235 @@ impl Channel {
stop_subscriber: Subscriber::new(), stop_subscriber: Subscriber::new(),
receive_task: StoppableTask::new(), receive_task: StoppableTask::new(),
stopped: Mutex::new(false), stopped: Mutex::new(false),
info: Mutex::new(ChannelInfo::new(channel_log)),
session, session,
info,
}) })
} }
pub async fn get_info(&self) -> serde_json::Value { /// Perform network handshake for message subsystem dispatchers.
self.info.lock().await.get_info().await async fn setup_dispatchers(subsystem: &MessageSubsystem) {
subsystem.add_dispatch::<message::VersionMessage>().await;
subsystem.add_dispatch::<message::VerackMessage>().await;
subsystem.add_dispatch::<message::PingMessage>().await;
subsystem.add_dispatch::<message::PongMessage>().await;
subsystem.add_dispatch::<message::GetAddrsMessage>().await;
subsystem.add_dispatch::<message::AddrsMessage>().await;
} }
/// Starts the channel. Runs a receive loop to start receiving messages or /// Fetch debug info, if any
/// handles a network failure. pub async fn get_info(&self) -> serde_json::Value {
if *self.p2p().dnet_enabled.lock().await {
// Maybe here we should panic? It probably should never be
// the case that dnet is enabled, but this stuff is empty.
// However it's possible that it somehow happens through a
// race condition, so let's be safe.
match self.info.lock().await.as_ref() {
Some(info) => info.get_info().await,
None => json!({}),
}
} else {
json!({})
}
}
/// Starts the channel. Runs a receive loop to start receiving messages
/// or handles a network failure.
pub fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) { pub fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) {
debug!(target: "net::channel::start()", "START, address={}", self.address()); debug!(target: "net::channel::start()", "START => address={}", self.address());
let self2 = self.clone();
let self_ = self.clone();
self.receive_task.clone().start( self.receive_task.clone().start(
self.clone().main_receive_loop(), self.clone().main_receive_loop(),
|result| self2.handle_stop(result), |result| self_.handle_stop(result),
Error::NetworkServiceStopped, Error::NetworkServiceStopped,
executor, executor,
); );
debug!(target: "net::channel::start()", "END, address={}", self.address());
debug!(target: "net::channel::start()", "END => address={}", self.address());
} }
/// Stops the channel. Steps through each component of the channel /// Stops the channel. Steps through each component of the channel connection
/// connection and sends a stop signal. Notifies all subscribers that /// and sends a stop signal. Notifies all subscribers that the channel has
/// the channel has been closed. /// been closed.
pub async fn stop(&self) { pub async fn stop(&self) {
debug!(target: "net::channel::stop()", "START, address={}", self.address()); debug!(target: "net::channel::stop()", "START => address={}", self.address());
if !(*self.stopped.lock().await) {
if !*self.stopped.lock().await {
*self.stopped.lock().await = true; *self.stopped.lock().await = true;
self.stop_subscriber.notify(Error::ChannelStopped).await; self.stop_subscriber.notify(Error::ChannelStopped).await;
self.receive_task.stop().await; self.receive_task.stop().await;
self.message_subsystem.trigger_error(Error::ChannelStopped).await; self.message_subsystem.trigger_error(Error::ChannelStopped).await;
debug!(target: "net::channel::stop()", "END, address={}", self.address());
} }
debug!(target: "net::channel::stop()", "END => address={}", self.address());
} }
/// Creates a subscription to a stopped signal. /// Creates a subscription to a stopped signal.
/// If the channel is stopped then this will return a ChannelStopped error. /// If the channel is stopped then this will return a ChannelStopped error.
pub async fn subscribe_stop(&self) -> Result<Subscription<Error>> { pub async fn subscribe_stop(&self) -> Result<Subscription<Error>> {
debug!(target: "net::channel::subscribe_stop()", "START, address={}", self.address()); debug!(target: "net::channel::subscribe_stop()", "START => address={}", self.address());
{ if *self.stopped.lock().await {
let stopped = *self.stopped.lock().await; return Err(Error::ChannelStopped)
if stopped {
return Err(Error::ChannelStopped)
}
} }
let sub = self.stop_subscriber.clone().subscribe().await; let sub = self.stop_subscriber.clone().subscribe().await;
debug!(target: "net::channel::subscribe_stop()", "END, address={}", self.address());
debug!(target: "net::channel::subscribe_stop()", "END => address={}", self.address());
Ok(sub) Ok(sub)
} }
/// Sends a message across a channel. Calls function 'send_message' that /// Sends a message across a channel. Calls `send_message` that creates
/// creates a new payload and sends it over the TCP connection as a /// a new payload and sends it over the network transport as a packet.
/// packet. Returns an error if something goes wrong. /// Returns an error if something goes wrong.
pub async fn send<M: message::Message>(&self, message: M) -> Result<()> { pub async fn send<M: message::Message>(&self, message: &M) -> Result<()> {
debug!( debug!(
target: "net::channel::send()", target: "net::channel::send()", "[START] command={} => address={}",
"START, command={:?}, address={}", M::NAME, self.address(),
M::name(),
self.address()
); );
{ if *self.stopped.lock().await {
let stopped = *self.stopped.lock().await; return Err(Error::ChannelStopped)
if stopped {
return Err(Error::ChannelStopped)
}
} }
// Catch failure and stop channel, return a net error // Catch failure and stop channel, return a net error
let result = match self.send_message(message).await { if let Err(e) = self.send_message(message).await {
Ok(()) => Ok(()), error!(
Err(err) => { target: "net::channel::send()", "[P2P]Channel send error for [{}]: {}",
error!(target: "net::channel::send()", "Channel send error for [{}]: {}", self.address(), err); self.address(), e
self.stop().await; );
Err(Error::ChannelStopped) self.stop().await;
} return Err(Error::ChannelStopped)
}; }
debug!( debug!(
target: "net::channel::send()", target: "net::channel::send()", "[END] command={} => address={}",
"END, command={:?}, address={}", M::NAME,self.address(),
M::name(),
self.address()
); );
{
let info = &mut *self.info.lock().await;
info.last_msg = M::name().to_string();
info.last_status = "sent".to_string();
}
result Ok(())
} }
/// Implements send message functionality. Creates a new payload and encodes /// Implements send message functionality. Creates a new payload and
/// it. Then creates a message packet- the base type of the network- and /// encodes it. Then creates a message packet (the base type of the
/// copies the payload into it. Then we send the packet over the TCP /// network) and copies the payload into it. Then we send the packet
/// stream. /// over the network stream.
async fn send_message<M: message::Message>(&self, message: M) -> Result<()> { async fn send_message<M: message::Message>(&self, message: &M) -> Result<()> {
let mut payload = Vec::new(); let packet = Packet { command: M::NAME.to_string(), payload: serialize(message) };
message.encode(&mut payload)?;
let packet = message::Packet { command: String::from(M::name()), payload };
let time = NanoTimestamp::current_time();
//let time = time::unix_timestamp()?;
{ dnet!(self,
let info = &mut *self.info.lock().await; let time = NanoTimestamp::current_time();
if let Some(l) = &info.log { let info_lock = self.info.lock().await;
l.lock().await.push((time, "send".to_string(), packet.command.clone())); let mut log = info_lock.as_ref().unwrap().log.lock().await;
}; log.push((time, "send".to_string(), packet.command.clone()));
} );
let stream = &mut *self.writer.lock().await; let stream = &mut *self.writer.lock().await;
message::send_packet(stream, packet).await let _written = message::send_packet(stream, packet).await?;
Ok(())
} }
/// Subscribe to a messages on the message subsystem. /// Subscribe to a message on the message subsystem.
pub async fn subscribe_msg<M: message::Message>(&self) -> Result<MessageSubscription<M>> { pub async fn subscribe_msg<M: message::Message>(&self) -> Result<MessageSubscription<M>> {
debug!( debug!(
target: "net::channel::subscribe_msg()", target: "net::channel::subscribe_msg()", "[START] command={} => address={}",
"START, command={:?}, address={}", M::NAME, self.address(),
M::name(),
self.address()
); );
let sub = self.message_subsystem.subscribe::<M>().await; let sub = self.message_subsystem.subscribe::<M>().await;
debug!( debug!(
target: "net::channel::subscribe_msg()", target: "net::channel::subscribe_msg()", "[END] command={} => address={}",
"END, command={:?}, address={}", M::NAME, self.address(),
M::name(),
self.address()
); );
sub sub
} }
/// Return the local socket address.
pub fn address(&self) -> Url {
self.address.clone()
}
pub async fn remote_node_id(&self) -> String {
self.info.lock().await.remote_node_id.clone()
}
pub async fn set_remote_node_id(&self, remote_node_id: String) {
self.info.lock().await.remote_node_id = remote_node_id;
}
/// End of file error. Triggered when unexpected end of file occurs.
fn is_eof_error(err: Error) -> bool {
match err {
Error::Io(io_err) => io_err == std::io::ErrorKind::UnexpectedEof,
_ => false,
}
}
/// Perform network handshake for message subsystem dispatchers.
async fn setup_dispatchers(message_subsystem: &MessageSubsystem) {
message_subsystem.add_dispatch::<message::VersionMessage>().await;
message_subsystem.add_dispatch::<message::VerackMessage>().await;
message_subsystem.add_dispatch::<message::PingMessage>().await;
message_subsystem.add_dispatch::<message::PongMessage>().await;
message_subsystem.add_dispatch::<message::GetAddrsMessage>().await;
message_subsystem.add_dispatch::<message::AddrsMessage>().await;
message_subsystem.add_dispatch::<message::ExtAddrsMessage>().await;
}
/// Convenience function that returns the Message Subsystem.
pub fn get_message_subsystem(&self) -> &MessageSubsystem {
&self.message_subsystem
}
/// Run the receive loop. Start receiving messages or handle network
/// failure.
async fn main_receive_loop(self: Arc<Self>) -> Result<()> {
debug!(target: "net::channel::main_receive_loop()", "START, address={}", self.address());
let reader = &mut *self.reader.lock().await;
loop {
let packet = match message::read_packet(reader).await {
Ok(packet) => packet,
Err(err) => {
if Self::is_eof_error(err.clone()) {
info!(
target: "net::channel::main_receive_loop()",
"Inbound connection {} disconnected",
self.address()
);
} else {
error!(
target: "net::channel::main_receive_loop()",
"Read error on channel {}: {}",
self.address(),
err
);
}
debug!(
target: "net::channel::main_receive_loop()",
"Channel::receive_loop() stopping channel {}",
self.address()
);
self.stop().await;
return Err(Error::ChannelStopped)
}
};
{
let info = &mut *self.info.lock().await;
info.last_msg = packet.command.clone();
info.last_status = "recv".to_string();
let time = NanoTimestamp::current_time();
//let time = time::unix_timestamp()?;
if let Some(l) = &info.log {
l.lock().await.push((time, "recv".to_string(), packet.command.clone()));
};
}
// Send result to our subscribers
self.message_subsystem.notify(&packet.command, packet.payload).await;
}
}
/// Handle network errors. Panic if error passes silently, otherwise /// Handle network errors. Panic if error passes silently, otherwise
/// broadcast the error. /// broadcast the error.
async fn handle_stop(self: Arc<Self>, result: Result<()>) { async fn handle_stop(self: Arc<Self>, result: Result<()>) {
debug!( debug!(target: "net::channel::handle_stop()", "[START] address={}", self.address());
target: "net::channel::handle_stop()",
"START, address={}",
self.address()
);
match result { match result {
Ok(()) => panic!("Channel task should never complete without error status"), Ok(()) => panic!("Channel task should never complete without error status"),
Err(err) => { // Send this error to all channel subscribers
// Send this error to all channel subscribers Err(e) => self.message_subsystem.trigger_error(e).await,
self.message_subsystem.trigger_error(err).await;
}
} }
debug!(
target: "net::channel::handle_stop()", debug!(target: "net::channel::handle_stop()", "[END] address={}", self.address());
"END, address={}", }
self.address()
); /// Run the receive loop. Start receiving messages or handle network failure.
async fn main_receive_loop(self: Arc<Self>) -> Result<()> {
debug!(target: "net::channel::main_receive_loop()", "[START] address={}", self.address());
// Acquire reader lock
let reader = &mut *self.reader.lock().await;
// Run loop
loop {
let packet = match message::read_packet(reader).await {
Ok(packet) => packet,
Err(err) => {
if Self::is_eof_error(&err) {
info!(
target: "net::channel::main_receive_loop()",
"[net] Channel inbound connecion {} disconnected",
self.address(),
);
} else {
error!(
target: "net::channel::main_receive_loop()",
"Read error on channel {}: {}",
self.address(), err,
);
}
debug!(
target: "net::channel::main_receive_loop()",
"Stopping channel {}", self.address(),
);
self.stop().await;
return Err(Error::ChannelStopped)
}
};
// Send result to our subscribers
self.message_subsystem.notify(&packet.command, &packet.payload).await;
}
}
/// Returns the local socket address
pub fn address(&self) -> &Url {
&self.address
}
/// Returns the inner [`MessageSubsystem`] reference
pub fn message_subsystem(&self) -> &MessageSubsystem {
&self.message_subsystem
} }
fn session(&self) -> Arc<dyn Session> { fn session(&self) -> Arc<dyn Session> {
self.session.upgrade().unwrap() self.session.upgrade().unwrap()
} }
pub fn session_type_id(&self) -> SessionBitflag { pub fn session_type_id(&self) -> SessionBitFlag {
let session = self.session(); let session = self.session();
session.type_id() session.type_id()
} }
fn p2p(&self) -> P2pPtr {
self.session().p2p()
}
fn is_eof_error(err: &Error) -> bool {
match err {
Error::Io(ioerr) => ioerr == &std::io::ErrorKind::UnexpectedEof,
_ => false,
}
}
} }

View File

@@ -16,104 +16,39 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{env, time::Duration}; use std::time::Duration;
use async_std::sync::Arc;
use log::error;
use url::Url; use url::Url;
use super::{ use super::{
transport::{NymTransport, TcpTransport, TorTransport, Transport, TransportName}, channel::{Channel, ChannelPtr},
Channel, ChannelPtr, SessionWeakPtr, SettingsPtr, session::SessionWeakPtr,
settings::SettingsPtr,
transport::Dialer,
}; };
use crate::{Error, Result}; use crate::Result;
/// Create outbound socket connections. /// Create outbound socket connections
pub struct Connector { pub struct Connector {
/// P2P settings
settings: SettingsPtr, settings: SettingsPtr,
/// Weak pointer to the session
pub session: SessionWeakPtr, pub session: SessionWeakPtr,
} }
impl Connector { impl Connector {
/// Create a new connector with default network settings. /// Create a new connector with given network settings
pub fn new(settings: SettingsPtr, session: SessionWeakPtr) -> Self { pub fn new(settings: SettingsPtr, session: SessionWeakPtr) -> Self {
Self { settings, session } Self { settings, session }
} }
/// Establish an outbound connection. /// Establish an outbound connection
pub async fn connect(&self, connect_url: Url) -> Result<ChannelPtr> { pub async fn connect(&self, endpoint: Url) -> Result<ChannelPtr> {
let transport_name = TransportName::try_from(connect_url.clone())?; let dialer = Dialer::new(endpoint.clone()).await?;
self.connect_channel( let timeout = Duration::from_secs(self.settings.outbound_connect_timeout);
connect_url, let ptstream = dialer.dial(Some(timeout)).await?;
transport_name,
Duration::from_secs(self.settings.connect_timeout_seconds.into()),
)
.await
}
async fn connect_channel( let channel = Channel::new(ptstream, endpoint, self.session.clone()).await;
&self, Ok(channel)
connect_url: Url,
transport_name: TransportName,
timeout: Duration,
) -> Result<Arc<Channel>> {
macro_rules! connect {
($stream:expr, $transport:expr, $upgrade:expr) => {{
if let Err(err) = $stream {
error!(target: "net::connector", "Setup for {} failed: {}", connect_url, err);
return Err(Error::ConnectFailed)
}
let stream = $stream?.await;
if let Err(err) = stream {
error!(target: "net::connector", "Connection to {} failed: {}", connect_url, err);
return Err(Error::ConnectFailed)
}
let channel = match $upgrade {
// session
None => {
Channel::new(Box::new(stream?), connect_url.clone(), self.session.clone())
.await
}
Some(u) if u == "tls" => {
let stream = $transport.upgrade_dialer(stream?)?.await;
Channel::new(Box::new(stream?), connect_url, self.session.clone()).await
}
Some(u) => return Err(Error::UnsupportedTransportUpgrade(u)),
};
Ok(channel)
}};
}
match transport_name {
TransportName::Tcp(upgrade) => {
let transport = TcpTransport::new(None, 1024);
let stream = transport.dial(connect_url.clone(), Some(timeout));
connect!(stream, transport, upgrade)
}
TransportName::Tor(upgrade) => {
let socks5_url = Url::parse(
&env::var("DARKFI_TOR_SOCKS5_URL")
.unwrap_or_else(|_| "socks5://127.0.0.1:9050".to_string()),
)?;
let transport = TorTransport::new(socks5_url, None)?;
let stream = transport.clone().dial(connect_url.clone(), None);
connect!(stream, transport, upgrade)
}
TransportName::Nym(upgrade) => {
let transport = NymTransport::new()?;
let stream = transport.clone().dial(connect_url.clone(), None);
connect!(stream, transport, upgrade)
}
_ => unimplemented!(),
}
} }
} }

View File

@@ -1,74 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Localnet addresses
pub const LOCALNET: [&str; 5] = ["localhost", "0.0.0.0", "[::]", "127.0.0.1", "[::1]"];
/// Illegal IPv6 addresses
pub const IP6_PRIV_RANGES: [&str; 2] = ["fc00::/7", "fec0::/10"];
/// Illegal IPv4 addresses
pub const IP4_PRIV_RANGES: [&str; 47] = [
"0.0.0.0/8",
"10.0.0.0/8",
"127.0.0.0/8",
"224.0.0.0/8",
"225.0.0.0/8",
"226.0.0.0/8",
"227.0.0.0/8",
"228.0.0.0/8",
"229.0.0.0/8",
"230.0.0.0/8",
"231.0.0.0/8",
"232.0.0.0/8",
"233.0.0.0/8",
"234.0.0.0/8",
"235.0.0.0/8",
"236.0.0.0/8",
"237.0.0.0/8",
"238.0.0.0/8",
"239.0.0.0/8",
"240.0.0.0/8",
"241.0.0.0/8",
"242.0.0.0/8",
"243.0.0.0/8",
"244.0.0.0/8",
"245.0.0.0/8",
"246.0.0.0/8",
"247.0.0.0/8",
"248.0.0.0/8",
"249.0.0.0/8",
"250.0.0.0/8",
"251.0.0.0/8",
"252.0.0.0/8",
"253.0.0.0/8",
"254.0.0.0/8",
"255.0.0.0/8",
"100.64.0.0/10",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"233.252.0.0/24",
"255.255.255.255/32",
];

View File

@@ -16,470 +16,233 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{ use std::collections::HashSet;
collections::{HashMap, HashSet},
net::IpAddr,
};
use async_std::sync::{Arc, Mutex}; use async_std::sync::{Arc, RwLock};
use ipnet::{Ipv4Net, Ipv6Net}; use log::debug;
use iprange::IpRange; use rand::{prelude::IteratorRandom, rngs::OsRng};
use log::{debug, error, warn};
use url::Url; use url::Url;
use super::constants::{IP4_PRIV_RANGES, IP6_PRIV_RANGES, LOCALNET}; use super::settings::SettingsPtr;
use crate::util::encoding::base32;
/// Pointer to hosts class. /// Atomic pointer to hosts object
pub type HostsPtr = Arc<Hosts>; pub type HostsPtr = Arc<Hosts>;
/// Manages a store of network addresses. /// Manages a store of network addresses
pub struct Hosts { pub struct Hosts {
addrs: Mutex<HashSet<Url>>, /// Set of stored addresses
localnet: bool, addrs: RwLock<HashSet<Url>>,
ipv4_range: IpRange<Ipv4Net>, /// Pointer to configured P2P settings
ipv6_range: IpRange<Ipv6Net>, settings: SettingsPtr,
} }
impl Hosts { impl Hosts {
/// Create a new host list. /// Create a new hosts list. Also initializes private IP ranges used
pub fn new(localnet: bool) -> Arc<Self> { /// for filtering.
// Initialize ipv4_range and ipv6_range if needed pub fn new(settings: SettingsPtr) -> HostsPtr {
let mut ipv4_range: IpRange<Ipv4Net> = Arc::new(Self { addrs: RwLock::new(HashSet::new()), settings })
IP4_PRIV_RANGES.iter().map(|s| s.parse().unwrap()).collect();
let mut ipv6_range: IpRange<Ipv6Net> =
IP6_PRIV_RANGES.iter().map(|s| s.parse().unwrap()).collect();
// These will make the trie potentially smaller
ipv4_range.simplify();
ipv6_range.simplify();
Arc::new(Self { addrs: Mutex::new(HashSet::new()), localnet, ipv4_range, ipv6_range })
} }
/// Add a new host to the host list, after filtering. /// Append given addrs to the known set. Filtering should be done externally.
pub async fn store(&self, input_addrs: Vec<Url>) { pub async fn store(&self, addrs: &[Url]) {
debug!(target: "net::hosts::store()", "hosts::store() [Start]"); debug!(target: "net::hosts::store()", "hosts::store() [START]");
let addrs = if !self.localnet {
let filtered = filter_localnet(input_addrs); let filtered_addrs = self.filter_addresses(addrs).await;
let filtered = filter_invalid(&self.ipv4_range, &self.ipv6_range, filtered);
filtered.into_keys().collect() if !filtered_addrs.is_empty() {
} else { let mut addrs_map = self.addrs.write().await;
debug!(target: "net::hosts::store()", "hosts::store() [Localnet mode, skipping filterring.]"); for addr in filtered_addrs {
input_addrs addrs_map.insert(addr);
}; }
let mut addrs_map = self.addrs.lock().await;
for addr in addrs {
addrs_map.insert(addr);
} }
debug!(target: "net::hosts::store()", "hosts::store() [End]");
debug!(target: "net::hosts::store()", "hosts::store() [END]");
} }
/// Add a new hosts external adders to the host list, after filtering and verifying /// Filter given addresses based on certain rulesets and validity.
/// the address url resolves to the provided connection address. async fn filter_addresses(&self, addrs: &[Url]) -> Vec<Url> {
pub async fn store_ext(&self, connection_addr: Url, input_addrs: Vec<Url>) { let mut ret = vec![];
debug!(target: "net::hosts::store_ext()", "hosts::store_ext() [Start]");
let addrs = if !self.localnet { for _addr in addrs {
let filtered = filter_localnet(input_addrs); // Validate that the format is `scheme://host_str:port`
let filtered = filter_invalid(&self.ipv4_range, &self.ipv6_range, filtered); if _addr.host_str().is_none() ||
filter_non_resolving(connection_addr, filtered) _addr.port().is_none() ||
} else { _addr.cannot_be_a_base() ||
debug!(target: "net::hosts::store_ext()", "hosts::store_ext() [Localnet mode, skipping filterring.]"); _addr.path_segments().is_some()
input_addrs {
}; continue
let mut addrs_map = self.addrs.lock().await; }
for addr in addrs {
addrs_map.insert(addr); // Our own addresses should never enter the hosts set.
let host_str = _addr.host_str().unwrap();
let mut got_own = false;
for ext in &self.settings.external_addrs {
if host_str == ext.host_str().unwrap() {
got_own = true;
break
}
}
if got_own {
continue
};
// We do this hack in order to parse IPs properly.
// https://github.com/whatwg/url/issues/749
let addr = Url::parse(&_addr.as_str().replace(_addr.scheme(), "http")).unwrap();
// Filter non-global ranges if we're not allowing localnet.
// Should never be allowed in production, so we don't really care
// about some of them (e.g. 0.0.0.0, or broadcast, etc.).
if !self.settings.localnet {
// Filter private IP ranges
match addr.host().unwrap() {
url::Host::Ipv4(ip) => {
if !ip.is_global() {
continue
}
}
url::Host::Ipv6(ip) => {
if !ip.is_global() {
continue
}
}
url::Host::Domain(d) => {
// TODO: This could perhaps be more exhaustive?
if d == "localhost" {
continue
}
}
}
}
// TODO: Should find a way to test the hosts are live without DNS leaks.
// Historically there is some code for this in cb73861bc13d3d5b43a6af931f29ce937e6fe681
// We could try to instantiate a channel and perform a handshake,
// although this seems kinda "heavy". Open to suggestions :)
ret.push(_addr.clone());
} }
debug!(target: "net::hosts::store_ext()", "hosts::store_ext() [End]");
ret
} }
/// Return the list of hosts.
pub async fn load_all(&self) -> Vec<Url> {
self.addrs.lock().await.iter().cloned().collect()
}
/// Remove an Url from the list
pub async fn remove(&self, url: &Url) -> bool { pub async fn remove(&self, url: &Url) -> bool {
self.addrs.lock().await.remove(url) self.addrs.write().await.remove(url)
} }
/// Check if the host list is empty. /// Check if the host list is empty.
pub async fn is_empty(&self) -> bool { pub async fn is_empty(&self) -> bool {
self.addrs.lock().await.is_empty() self.addrs.read().await.is_empty()
}
}
/// Auxiliary function to filter localnet hosts.
fn filter_localnet(input_addrs: Vec<Url>) -> Vec<Url> {
debug!(target: "net::hosts::filter_localnet()", "hosts::filter_localnet() [Input addresses: {:?}]", input_addrs);
let mut filtered = vec![];
for addr in &input_addrs {
if let Some(host_str) = addr.host_str() {
if !LOCALNET.contains(&host_str) {
filtered.push(addr.clone());
continue
}
debug!(target: "net::hosts::filter_localnet()", "hosts::filter_localnet() [Filtered localnet addr: {}]", addr);
continue
}
warn!(target: "net::hosts::filter_localnet()", "hosts::filter_localnet() [{} addr.host_str is empty, skipping.]", addr);
} }
debug!(target: "net::hosts::filter_localnet()", "hosts::filter_localnet() [Filtered addresses: {:?}]", filtered); /// Check if host is already in the set
filtered pub async fn contains(&self, addr: &Url) -> bool {
} self.addrs.read().await.contains(addr)
/// Auxiliary function to filter invalid(unresolvable) hosts.
fn filter_invalid(
ipv4_range: &IpRange<Ipv4Net>,
ipv6_range: &IpRange<Ipv6Net>,
input_addrs: Vec<Url>,
) -> HashMap<Url, Vec<IpAddr>> {
debug!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Input addresses: {:?}]", input_addrs);
let mut filtered = HashMap::new();
for addr in &input_addrs {
// Discard domainless Urls
let domain = match addr.domain() {
Some(d) => d,
None => {
debug!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Filtered domainless url: {}]", addr);
continue
}
};
// Validate onion domain
if domain.ends_with("onion") {
match is_valid_onion(domain) {
true => {
filtered.insert(addr.clone(), vec![]);
}
false => {
warn!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Got invalid onion address: {}]", addr)
}
}
continue
}
// Validate Internet domains and IPs. socket_addrs() does a resolution
// with the local DNS resolver (i.e. /etc/resolv.conf), so the admin has
// to take care of any DNS leaks by properly configuring their system for
// DNS resolution.
if let Ok(socket_addrs) = addr.socket_addrs(|| None) {
// Check if domain resolved to anything
if socket_addrs.is_empty() {
debug!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Filtered unresolvable URL: {}]", addr);
continue
}
// Checking resolved IP validity
let mut resolves = vec![];
for i in socket_addrs {
let ip = i.ip();
match ip {
IpAddr::V4(a) => {
if ipv4_range.contains(&a) {
debug!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Filtered private-range IPv4: {}]", a);
continue
}
}
IpAddr::V6(a) => {
if ipv6_range.contains(&a) {
debug!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Filtered private range IPv6: {}]", a);
continue
}
}
}
resolves.push(ip);
}
if resolves.is_empty() {
debug!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Filtered unresolvable URL: {}]", addr);
continue
}
filtered.insert(addr.clone(), resolves);
} else {
warn!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Failed resolving socket_addrs for {}]", addr);
continue
}
} }
debug!(target: "net::hosts::filter_invalid()", "hosts::filter_invalid() [Filtered addresses: {:?}]", filtered); /// Return all known hosts
filtered pub async fn load_all(&self) -> Vec<Url> {
} self.addrs.read().await.iter().cloned().collect()
}
/// Filters `input_addrs` keys to whatever has at least one `IpAddr` that is /// Get up to n random hosts from the hosts set.
/// the same as `connection_addr`'s IP address. pub async fn get_n_random(&self, n: u32) -> Vec<Url> {
/// Skips .onion domains. let n = n as usize;
fn filter_non_resolving(connection_addr: Url, input_addrs: HashMap<Url, Vec<IpAddr>>) -> Vec<Url> { let addrs = self.addrs.read().await;
debug!(target: "net::hosts::filter_non_resolving()", "hosts::filter_non_resolving() [Input addresses: {:?}]", input_addrs); let urls = addrs.iter().choose_multiple(&mut OsRng, n.min(addrs.len()));
debug!(target: "net::hosts::filter_non_resolving()", "hosts::filter_non_resolving() [Connection address: {}]", connection_addr); let urls = urls.iter().map(|&url| url.clone()).collect();
urls
}
// Retrieve connection IPs /// Get all peers that match the given transport schemes from the hosts set.
let mut ipv4_range = vec![]; pub async fn load_with_schemes(&self, schemes: &[String]) -> Vec<Url> {
let mut ipv6_range = vec![]; let mut ret = vec![];
match connection_addr.socket_addrs(|| None) { for addr in self.addrs.read().await.iter() {
Ok(v) => { if schemes.contains(&addr.scheme().to_string()) {
for i in v { ret.push(addr.clone());
match i.ip() {
IpAddr::V4(a) => ipv4_range.push(a),
IpAddr::V6(a) => ipv6_range.push(a),
}
}
}
Err(e) => {
error!(target: "net::hosts::filter_non_resolving()", "hosts::filter_non_resolving() [Failed resolving connection_addr {}: {}]", connection_addr, e);
return vec![]
}
};
debug!(target: "net::hosts::filter_non_resolving()", "hosts::filter_non_resolving() [{} IPv4: {:?}]", connection_addr, ipv4_range);
debug!(target: "net::hosts::filter_non_resolving()", "hosts::filter_non_resolving() [{} IPv6: {:?}]", connection_addr, ipv6_range);
let mut filtered = vec![];
for (addr, resolves) in &input_addrs {
// Keep onion domains. It's assumed that the .onion addresses
// have already been validated.
let addr_domain = addr.domain().unwrap();
if addr_domain.ends_with(".onion") {
filtered.push(addr.clone());
continue
}
// Checking IP validity. If at least one IP matches, we consider it fine.
let mut valid = false;
for ip in resolves {
match ip {
IpAddr::V4(a) => {
if ipv4_range.contains(a) {
valid = true;
break
}
}
IpAddr::V6(a) => {
if ipv6_range.contains(a) {
valid = true;
break
}
}
} }
} }
if !valid { ret
debug!(target: "net::hosts::filter_non_resolving()", "hosts::filter_non_resolving() [Filtered unresolvable url: {}]", addr);
continue
}
filtered.push(addr.clone());
} }
debug!(target: "net::hosts::filter_non_resolving()", "hosts::filter_non_resolving() [Filtered addresses: {:?}]", filtered);
filtered
}
/// Validate a given .onion address. Currently it just checks that the
/// length and encoding are ok, and does not do any deeper check. Should
/// be fixed in the future when arti is ready.
fn is_valid_onion(onion: &str) -> bool {
let onion = match onion.strip_suffix(".onion") {
Some(s) => s,
None => onion,
};
if onion.len() != 56 {
return false
}
base32::decode(&onion.to_uppercase()).is_some()
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{ use super::{super::settings::Settings, *};
collections::{HashMap, HashSet},
net::{IpAddr, Ipv4Addr},
};
use ipnet::{Ipv4Net, Ipv6Net}; #[async_std::test]
use iprange::IpRange; async fn test_store_localnet() {
use url::Url; let mut settings = Settings::default();
settings.localnet = true;
settings.external_addrs = vec![
Url::parse("tcp://foo.bar:123").unwrap(),
Url::parse("tcp://lol.cat:321").unwrap(),
];
use crate::net::{ let hosts = Hosts::new(Arc::new(settings.clone()));
constants::{IP4_PRIV_RANGES, IP6_PRIV_RANGES}, hosts.store(&settings.external_addrs).await;
hosts::{filter_invalid, filter_localnet, filter_non_resolving, is_valid_onion}, assert!(hosts.is_empty().await);
};
#[test] let local_hosts = vec![
fn test_filter_localnet() { Url::parse("tcp://localhost:3921").unwrap(),
// Uncomment for inner logging Url::parse("tcp://127.0.0.1:23957").unwrap(),
/* Url::parse("tcp://[::1]:21481").unwrap(),
simplelog::TermLogger::init( Url::parse("tcp://192.168.10.65:311").unwrap(),
simplelog::LevelFilter::Debug, Url::parse("tcp://0.0.0.0:2312").unwrap(),
simplelog::Config::default(), Url::parse("tcp://255.255.255.255:2131").unwrap(),
simplelog::TerminalMode::Mixed, ];
simplelog::ColorChoice::Auto, hosts.store(&local_hosts).await;
) for i in local_hosts {
.unwrap(); assert!(hosts.contains(&i).await);
*/ }
// Create addresses to test let remote_hosts = vec![
let valid = Url::parse("tls://facebook.com:13333").unwrap(); Url::parse("tcp://dark.fi:80").unwrap(),
let onion = Url::parse( Url::parse("tcp://top.kek:111").unwrap(),
"tor://facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:13333", Url::parse("tcp://http.cat:401").unwrap(),
) ];
.unwrap(); hosts.store(&remote_hosts).await;
let localhost = Url::parse("tls://localhost:13333").unwrap(); for i in remote_hosts {
let localip = Url::parse("tls://127.0.0.1:13333").unwrap(); assert!(hosts.contains(&i).await);
}
// Create input addresses vector
let input_addrs = vec![valid.clone(), onion.clone(), localhost, localip];
// Create expected output addresses vector
let output_addrs = vec![valid, onion];
let output_addrs: HashSet<&Url> = HashSet::from_iter(output_addrs.iter());
// Execute filtering for v4 addr
let filtered = filter_localnet(input_addrs);
let filtered: HashSet<&Url> = HashSet::from_iter(filtered.iter());
// Validate filtered addresses
assert_eq!(output_addrs, filtered);
} }
#[test] #[async_std::test]
fn test_filter_invalid() { async fn test_store() {
// Uncomment for inner logging let mut settings = Settings::default();
/* settings.localnet = false;
TermLogger::init( settings.external_addrs = vec![
LevelFilter::Debug, Url::parse("tcp://foo.bar:123").unwrap(),
Config::default(), Url::parse("tcp://lol.cat:321").unwrap(),
TerminalMode::Mixed, ];
ColorChoice::Auto,
)
.unwrap();
*/
// Initialize ipv4_range and ipv6_range if needed let hosts = Hosts::new(Arc::new(settings.clone()));
let mut ipv4_range: IpRange<Ipv4Net> = hosts.store(&settings.external_addrs).await;
IP4_PRIV_RANGES.iter().map(|s| s.parse().unwrap()).collect(); assert!(hosts.is_empty().await);
let mut ipv6_range: IpRange<Ipv6Net> =
IP6_PRIV_RANGES.iter().map(|s| s.parse().unwrap()).collect();
// These will make the trie potentially smaller let local_hosts = vec![
ipv4_range.simplify(); Url::parse("tcp://localhost:3921").unwrap(),
ipv6_range.simplify(); Url::parse("tcp://127.0.0.1:23957").unwrap(),
Url::parse("tor://[::1]:21481").unwrap(),
Url::parse("tcp://192.168.10.65:311").unwrap(),
Url::parse("tcp+tls://0.0.0.0:2312").unwrap(),
Url::parse("tcp://255.255.255.255:2131").unwrap(),
];
hosts.store(&local_hosts).await;
for i in local_hosts {
assert!(!hosts.contains(&i).await);
}
// Create addresses to test let remote_hosts = vec![
let valid = Url::parse("tls://facebook.com:13333").unwrap(); Url::parse("tcp://dark.fi:80").unwrap(),
let domainless = Url::parse("unix:/run/foo.socket").unwrap(); Url::parse("tcp://http.cat:401").unwrap(),
let mut hostless = Url::parse("tls://185.60.216.35:13333").unwrap(); Url::parse("tcp://foo.bar:111").unwrap(),
hostless.set_host(None).unwrap(); ];
let onion = Url::parse( hosts.store(&remote_hosts).await;
"tor://facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:13333", assert!(hosts.contains(&remote_hosts[0]).await);
) assert!(hosts.contains(&remote_hosts[1]).await);
.unwrap(); assert!(!hosts.contains(&remote_hosts[2]).await);
let invalid_onion =
Url::parse("tor://facebookwemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:13333")
.unwrap();
// Create input addresses vector
let input_addrs = vec![valid.clone(), domainless, hostless, onion.clone(), invalid_onion];
// Create expected output addresses vector
let output_addrs = vec![valid, onion];
let output_addrs: HashSet<&Url> = HashSet::from_iter(output_addrs.iter());
// Execute filtering for v4 addr
let filtered = filter_invalid(&ipv4_range, &ipv6_range, input_addrs);
let filtered: Vec<Url> = filtered.into_iter().map(|(k, _)| k).collect();
let filtered: HashSet<&Url> = HashSet::from_iter(filtered.iter());
// Validate filtered addresses
assert_eq!(output_addrs, filtered);
}
#[test]
fn test_filter_non_resolving() {
// Uncomment for inner logging
/*
TermLogger::init(
LevelFilter::Debug,
Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
)
.unwrap();
*/
// Create addresses to test
let connection_url_v4 = Url::parse("tls://185.60.216.35:13333").unwrap();
let connection_url_v6 =
Url::parse("tls://[2a03:2880:f12d:83:face:b00c:0:25de]:13333").unwrap();
let fake_connection_url = Url::parse("tls://185.199.109.153:13333").unwrap();
let resolving_url = Url::parse("tls://facebook.com:13333").unwrap();
let random_url = Url::parse("tls://facebookkk.com:13333").unwrap();
let onion = Url::parse(
"tor://facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:13333",
)
.unwrap();
// Create input addresses hashmap, containing created addresses, excluding connection url
let mut input_addrs = HashMap::new();
input_addrs.insert(
resolving_url.clone(),
vec![
IpAddr::V4(Ipv4Addr::new(185, 60, 216, 35)),
"2a03:2880:f12d:83:face:b00c:0:25de".parse().unwrap(),
],
);
input_addrs.insert(random_url, vec![]);
input_addrs.insert(onion.clone(), vec![]);
// Create expected output addresses hashset
let mut output_addrs = HashMap::new();
output_addrs.insert(
resolving_url,
vec![
IpAddr::V4(Ipv4Addr::new(185, 60, 216, 35)),
"2a03:2880:f12d:83:face:b00c:0:25de".parse().unwrap(),
],
);
output_addrs.insert(onion.clone(), vec![]);
// Convert hashmap to Vec<Url and then to hashset, to ignore shuffling
let output_addrs: Vec<Url> = output_addrs.into_iter().map(|(k, _)| k).collect();
let output_addrs: HashSet<&Url> = HashSet::from_iter(output_addrs.iter());
let mut fake_output_addrs: HashMap<Url, Vec<Url>> = HashMap::new();
// Onion addresses don't get filtered, as we can't resolve them
fake_output_addrs.insert(onion, vec![]);
let fake_output_addrs: Vec<Url> = fake_output_addrs.into_iter().map(|(k, _)| k).collect();
let fake_output_addrs: HashSet<&Url> = HashSet::from_iter(fake_output_addrs.iter());
// Execute filtering for v4 addr
let filtered = filter_non_resolving(connection_url_v4, input_addrs.clone());
let filtered = HashSet::from_iter(filtered.iter());
// Validate filtered addresses
assert_eq!(output_addrs, filtered);
// Execute filtering for v6 addr
let filtered = filter_non_resolving(connection_url_v6, input_addrs.clone());
let filtered = HashSet::from_iter(filtered.iter());
assert_eq!(output_addrs, filtered);
// Execute filtering for fake addr
let filtered = filter_non_resolving(fake_connection_url, input_addrs);
let filtered = HashSet::from_iter(filtered.iter());
assert_eq!(fake_output_addrs, filtered);
}
#[test]
fn test_is_valid_onion() {
// Valid onion
assert!(is_valid_onion("facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"),);
// Valid onion without .onion suffix
assert!(is_valid_onion("facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd"),);
// Invalid onion
assert!(!is_valid_onion("facebook.com"));
} }
} }

View File

@@ -26,157 +26,134 @@ use crate::{Error, Result};
const MAGIC_BYTES: [u8; 4] = [0xd9, 0xef, 0xb6, 0x7d]; const MAGIC_BYTES: [u8; 4] = [0xd9, 0xef, 0xb6, 0x7d];
/// Generic message template. /// Generic message template.
pub trait Message: 'static + Encodable + Decodable + Send + Sync { pub trait Message: 'static + Send + Sync + Encodable + Decodable {
fn name() -> &'static str; const NAME: &'static str;
} }
/// Outbound keep-alive message. #[macro_export]
#[derive(SerialEncodable, SerialDecodable)] macro_rules! impl_p2p_message {
($st:ty, $nm:expr) => {
impl Message for $st {
const NAME: &'static str = $nm;
}
};
}
/// Outbound keepalive message.
#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
pub struct PingMessage { pub struct PingMessage {
pub nonce: u32, pub nonce: u16,
} }
impl_p2p_message!(PingMessage, "ping");
/// Inbound keep-alive message. /// Inbound keepalive message.
#[derive(SerialEncodable, SerialDecodable)] #[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
pub struct PongMessage { pub struct PongMessage {
pub nonce: u32, pub nonce: u16,
} }
impl_p2p_message!(PongMessage, "pong");
/// Requests address of outbound connection. /// Requests address of outbound connecction.
#[derive(SerialEncodable, SerialDecodable)] #[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
pub struct GetAddrsMessage {} pub struct GetAddrsMessage {
/// Maximum number of addresses to receive
pub max: u32,
}
impl_p2p_message!(GetAddrsMessage, "getaddr");
/// Sends address information to inbound connection. Response to GetAddrs /// Sends address information to inbound connection.
/// message. /// Response to `GetAddrsMessage`.
#[derive(SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct AddrsMessage { pub struct AddrsMessage {
pub addrs: Vec<Url>, pub addrs: Vec<Url>,
} }
impl_p2p_message!(AddrsMessage, "addr");
/// Sends external address information to inbound connection.
#[derive(SerialEncodable, SerialDecodable)]
pub struct ExtAddrsMessage {
pub ext_addrs: Vec<Url>,
}
/// Requests version information of outbound connection. /// Requests version information of outbound connection.
#[derive(SerialEncodable, SerialDecodable)] #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct VersionMessage { pub struct VersionMessage {
/// Only used for debugging. Compromises privacy when set.
pub node_id: String, pub node_id: String,
} }
impl_p2p_message!(VersionMessage, "version");
/// Sends version information to inbound connection. Response to VersionMessage. /// Sends version information to inbound connection.
#[derive(SerialEncodable, SerialDecodable)] /// Response to `VersionMessage`.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct VerackMessage { pub struct VerackMessage {
// app version /// App version
pub app: String, pub app_version: semver::Version,
} }
impl_p2p_message!(VerackMessage, "verack");
impl Message for PingMessage { /// Packets are the base type read from the network.
fn name() -> &'static str { /// Converted to messages and passed to event loop.
"ping" #[derive(Debug, SerialEncodable, SerialDecodable)]
}
}
impl Message for PongMessage {
fn name() -> &'static str {
"pong"
}
}
impl Message for GetAddrsMessage {
fn name() -> &'static str {
"getaddr"
}
}
impl Message for AddrsMessage {
fn name() -> &'static str {
"addr"
}
}
impl Message for ExtAddrsMessage {
fn name() -> &'static str {
"extaddr"
}
}
impl Message for VersionMessage {
fn name() -> &'static str {
"version"
}
}
impl Message for VerackMessage {
fn name() -> &'static str {
"verack"
}
}
/// Packets are the base type read from the network. Converted to messages and
/// passed to event loop.
pub struct Packet { pub struct Packet {
pub command: String, pub command: String,
pub payload: Vec<u8>, pub payload: Vec<u8>,
} }
/// Reads and decodes an inbound payload. /// Reads and decodes an inbound payload from the given async stream.
/// Returns decoded [`Packet`].
pub async fn read_packet<R: AsyncRead + Unpin + Sized>(stream: &mut R) -> Result<Packet> { pub async fn read_packet<R: AsyncRead + Unpin + Sized>(stream: &mut R) -> Result<Packet> {
// Packets have a 4 byte header of magic digits // Packets should have a 4 byte header of magic digits.
// This is used for network debugging // This is used for network debugging.
let mut magic = [0u8; 4]; let mut magic = [0u8; 4];
debug!(target: "net::message", "reading magic..."); debug!(target: "net::message", "Reading magic...");
stream.read_exact(&mut magic).await?; stream.read_exact(&mut magic).await?;
debug!(target: "net::message", "read magic {:?}", magic); debug!(target: "net::message", "Read magic {:?}", magic);
if magic != MAGIC_BYTES { if magic != MAGIC_BYTES {
debug!(target: "net::message", "Error: Magic bytes mismatch");
return Err(Error::MalformedPacket) return Err(Error::MalformedPacket)
} }
// The type of the message // The type of the message.
let command_len = VarInt::decode_async(stream).await?.0 as usize; let command_len = VarInt::decode_async(stream).await?.0 as usize;
let mut cmd = vec![0u8; command_len]; let mut cmd = vec![0u8; command_len];
if command_len > 0 { stream.read_exact(&mut cmd).await?;
stream.read_exact(&mut cmd).await?; let command = String::from_utf8(cmd)?;
} debug!(target: "net::message", "Read command: {}", command);
let cmd = String::from_utf8(cmd)?;
debug!(target: "net::message", "read command: {}", cmd);
let payload_len = VarInt::decode_async(stream).await?.0 as usize;
// The message-dependent data (see message types) // The message-dependent data (see message types)
let payload_len = VarInt::decode_async(stream).await?.0 as usize;
let mut payload = vec![0u8; payload_len]; let mut payload = vec![0u8; payload_len];
if payload_len > 0 { stream.read_exact(&mut payload).await?;
stream.read_exact(&mut payload).await?; debug!(target: "net::message", "Read payload {} bytes", payload_len);
}
debug!(target: "net::message", "read payload {} bytes", payload_len);
Ok(Packet { command: cmd, payload }) Ok(Packet { command, payload })
} }
/// Sends an outbound packet by writing data to TCP stream. /// Sends an outbound packet by writing data to the given async stream.
/// Returns the total written bytes.
pub async fn send_packet<W: AsyncWrite + Unpin + Sized>( pub async fn send_packet<W: AsyncWrite + Unpin + Sized>(
stream: &mut W, stream: &mut W,
packet: Packet, packet: Packet,
) -> Result<()> { ) -> Result<usize> {
debug!(target: "net::message", "sending magic...");
stream.write_all(&MAGIC_BYTES).await?;
debug!(target: "net::message", "sent magic...");
VarInt(packet.command.len() as u64).encode_async(stream).await?;
assert!(!packet.command.is_empty()); assert!(!packet.command.is_empty());
stream.write_all(packet.command.as_bytes()).await?; assert!(!packet.payload.is_empty());
debug!(target: "net::message", "sent command: {}", packet.command);
assert!(std::mem::size_of::<usize>() <= std::mem::size_of::<u64>()); assert!(std::mem::size_of::<usize>() <= std::mem::size_of::<u64>());
VarInt(packet.payload.len() as u64).encode_async(stream).await?;
if !packet.payload.is_empty() { let mut written: usize = 0;
stream.write_all(&packet.payload).await?;
}
debug!(target: "net::message", "sent payload {} bytes", packet.payload.len() as u64);
Ok(()) debug!(target: "net::message", "Sending magic...");
stream.write_all(&MAGIC_BYTES).await?;
written += MAGIC_BYTES.len();
debug!(target: "net::message", "Sent magic");
debug!(target: "net::message", "Sending command...");
written += VarInt(packet.command.len() as u64).encode_async(stream).await?;
let cmd_ref = packet.command.as_bytes();
stream.write_all(cmd_ref).await?;
written += cmd_ref.len();
debug!(target: "net::message", "Sent command: {}", packet.command);
debug!(target: "net::message", "Sending payload...");
written += VarInt(packet.payload.len() as u64).encode_async(stream).await?;
stream.write_all(&packet.payload).await?;
written += packet.payload.len();
debug!(target: "net::message", "Sent payload {} bytes", packet.payload.len() as u64);
Ok(written)
} }

View File

@@ -16,23 +16,119 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{any::Any, collections::HashMap, io::Cursor, sync::Arc}; use std::{any::Any, collections::HashMap, io::Cursor};
use async_std::sync::Mutex; use async_std::sync::{Arc, Mutex};
use async_trait::async_trait; use async_trait::async_trait;
use futures::stream::{FuturesUnordered, StreamExt};
use log::{debug, warn}; use log::{debug, warn};
use rand::Rng; use rand::{rngs::OsRng, Rng};
use crate::{Error, Result};
use super::message::Message; use super::message::Message;
use crate::{Error, Result};
/// 64bit identifier for message subscription. /// 64-bit identifier for message subscription.
pub type MessageSubscriptionId = u64; pub type MessageSubscriptionId = u64;
type MessageResult<M> = Result<Arc<M>>; type MessageResult<M> = Result<Arc<M>>;
/// Handles message subscriptions through a subscription ID and a receiver /// A dispatcher that is unique to every [`Message`].
/// channel. /// Maintains a list of subscribers that are subscribed to that
/// unique Message type and handles sending messages across these
/// subscriptions.
#[derive(Debug)]
struct MessageDispatcher<M: Message> {
subs: Mutex<HashMap<MessageSubscriptionId, smol::channel::Sender<MessageResult<M>>>>,
}
impl<M: Message> MessageDispatcher<M> {
/// Create a new message dispatcher
fn new() -> Self {
Self { subs: Mutex::new(HashMap::new()) }
}
/// Create a random ID.
fn random_id() -> MessageSubscriptionId {
//let mut rng = rand::thread_rng();
OsRng.gen()
}
/// Subscribe to a channel.
/// Assigns a new ID and adds it to the list of subscribers.
pub async fn subscribe(self: Arc<Self>) -> MessageSubscription<M> {
let (sender, recv_queue) = smol::channel::unbounded();
// Guard against overwriting
let mut id = Self::random_id();
let mut subs = self.subs.lock().await;
loop {
if subs.contains_key(&id) {
id = Self::random_id();
continue
}
subs.insert(id, sender);
break
}
drop(subs);
MessageSubscription { id, recv_queue, parent: self }
}
/// Unsubscribe from a channel.
/// Removes the associated ID from the subscriber list.
async fn unsubscribe(&self, sub_id: MessageSubscriptionId) {
self.subs.lock().await.remove(&sub_id);
}
/// Private function to concurrently transmit a message to all subscriber channels.
/// Automatically clear all inactive channels. Strictly used internally.
async fn _trigger_all(&self, message: MessageResult<M>) {
let mut subs = self.subs.lock().await;
debug!(
target: "net::message_subscriber::_trigger_all()", "START msg={}({}), subs={}",
if message.is_ok() { "Ok" } else {"Err"},
M::NAME, subs.len(),
);
let mut futures = FuturesUnordered::new();
let mut garbage_ids = vec![];
// Prep the futures for concurrent execution
for (sub_id, sub) in &*subs {
let sub_id = *sub_id;
let sub = sub.clone();
let message = message.clone();
futures.push(async move {
match sub.send(message).await {
Ok(res) => Ok((sub_id, res)),
Err(err) => Err((sub_id, err)),
}
});
}
// Start polling
while let Some(r) = futures.next().await {
if let Err((sub_id, _err)) = r {
garbage_ids.push(sub_id);
}
}
// Garbage cleanup
for sub_id in garbage_ids {
subs.remove(&sub_id);
}
debug!(
target: "net::message_subscriber::_trigger_all()", "END msg={}({}), subs={}",
if message.is_ok() { "Ok" } else { "Err" },
M::NAME, subs.len(),
);
}
}
/// Handles message subscriptions through a subscription ID and
/// a receiver channel.
#[derive(Debug)]
pub struct MessageSubscription<M: Message> { pub struct MessageSubscription<M: Message> {
id: MessageSubscriptionId, id: MessageSubscriptionId,
recv_queue: smol::channel::Receiver<MessageResult<M>>, recv_queue: smol::channel::Receiver<MessageResult<M>>,
@@ -44,141 +140,65 @@ impl<M: Message> MessageSubscription<M> {
pub async fn receive(&self) -> MessageResult<M> { pub async fn receive(&self) -> MessageResult<M> {
match self.recv_queue.recv().await { match self.recv_queue.recv().await {
Ok(message) => message, Ok(message) => message,
Err(err) => { Err(e) => panic!("MessageSubscription::receive(): recv_queue failed! {}", e),
panic!("MessageSubscription::receive() recv_queue failed! {}", err);
}
} }
} }
/// Unsubscribe from a message subscription. Must be called manually. /// Unsubscribe from a message subscription. Must be called manually.
pub async fn unsubscribe(&self) { pub async fn unsubscribe(&self) {
self.parent.clone().unsubscribe(self.id).await self.parent.unsubscribe(self.id).await
} }
} }
/// Generic interface for the message dispatcher.
#[async_trait] #[async_trait]
/// Generic interface for message dispatcher.
trait MessageDispatcherInterface: Send + Sync { trait MessageDispatcherInterface: Send + Sync {
async fn trigger(&self, payload: Vec<u8>); async fn trigger(&self, payload: &[u8]);
async fn trigger_error(&self, err: Error); async fn trigger_error(&self, err: Error);
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>; fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
} }
/// A dispatchers that is unique to every Message. Maintains a list of subscribers that are subscribed to that unique Message type and handles sending messages across these subscriptions. /// Local implementation of the Message Dispatcher Interface
struct MessageDispatcher<M: Message> {
subs: Mutex<HashMap<MessageSubscriptionId, smol::channel::Sender<MessageResult<M>>>>,
}
impl<M: Message> MessageDispatcher<M> {
/// Create a new message dispatcher.
fn new() -> Self {
MessageDispatcher { subs: Mutex::new(HashMap::new()) }
}
/// Create a random ID.
fn random_id() -> MessageSubscriptionId {
let mut rng = rand::thread_rng();
rng.gen()
}
/// Subscribe to a channel. Assigns a new ID and adds it to the list of
/// subscribers.
pub async fn subscribe(self: Arc<Self>) -> MessageSubscription<M> {
let (sender, recvr) = smol::channel::unbounded();
let sub_id = Self::random_id();
self.subs.lock().await.insert(sub_id, sender);
MessageSubscription { id: sub_id, recv_queue: recvr, parent: self }
}
/// Unsubcribe from a channel. Removes the associated ID from the subscriber
/// list.
async fn unsubscribe(&self, sub_id: MessageSubscriptionId) {
self.subs.lock().await.remove(&sub_id);
}
/// Private function to transmit a message to all subscriber channels. Automatically clear inactive
/// channels. Used strictly internally.
async fn _trigger_all(&self, message: MessageResult<M>) {
debug!(
target: "net::message_subscriber::_trigger_all()",
"START, message={}({}), subs={}",
if message.is_ok() { "Ok" } else { "Err" },
M::name(),
self.subs.lock().await.len()
);
let mut garbage_ids = Vec::new();
for (sub_id, sub) in &*self.subs.lock().await {
match sub.send(message.clone()).await {
Ok(()) => {}
Err(_err) => {
// Automatically clean out closed channels
garbage_ids.push(*sub_id);
// panic!("Error returned sending message in notify() call!
// {}", err);
}
}
}
self.collect_garbage(garbage_ids).await;
debug!(
target: "net::message_subscriber::_trigger_all()",
"END, msg={}({}), subs={}",
if message.is_ok() { "Ok" } else { "Err" },
M::name(),
self.subs.lock().await.len()
);
}
/// Remove inactive channels.
async fn collect_garbage(&self, ids: Vec<MessageSubscriptionId>) {
let mut subs = self.subs.lock().await;
for id in &ids {
subs.remove(id);
}
}
}
#[async_trait] #[async_trait]
// Local implementation of the Message Dispatcher Interface.
impl<M: Message> MessageDispatcherInterface for MessageDispatcher<M> { impl<M: Message> MessageDispatcherInterface for MessageDispatcher<M> {
/// Internal function to deserialize data into a message type and dispatch it across subscriber channels. /// Internal function to deserialize data into a message type
async fn trigger(&self, payload: Vec<u8>) { /// and dispatch it across subscriber channels.
// deserialize data into type async fn trigger(&self, payload: &[u8]) {
// send down the pipes // Deserialize data into type, send down the pipes.
let cursor = Cursor::new(payload); let cursor = Cursor::new(payload);
match M::decode(cursor) { match M::decode(cursor) {
Ok(message) => { Ok(message) => {
let message = Ok(Arc::new(message)); let message = Ok(Arc::new(message));
self._trigger_all(message).await self._trigger_all(message).await
} }
Err(err) => { Err(err) => {
debug!( debug!(
target: "net::message_subscriber::trigger()", target: "net::message_subscriber::trigger()",
"Unable to decode data. Dropping...: {}", "Unable to decode data. Dropping...: {}",
err err,
); );
} }
} }
} }
/// Interal function that sends a Error message to all subscriber channels. /// Internal function that sends an error message to all subscriber channels.
async fn trigger_error(&self, err: Error) { async fn trigger_error(&self, err: Error) {
self._trigger_all(Err(err)).await; self._trigger_all(Err(err)).await;
} }
/// Converts to Any trait. Enables the dynamic modification of static types. /// Converts to `Any` trait. Enables the dynamic modification of static types.
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> { fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self self
} }
} }
/// Generic publish/subscribe class that maintains a list of dispatchers. Dispatchers transmit /// Generic publish/subscribe class that maintains a list of dispatchers.
/// messages to subscribers and are specific to one message type. /// Dispatchers transmit messages to subscribers and are specific to one
/// message type.
#[derive(Default)]
pub struct MessageSubsystem { pub struct MessageSubsystem {
dispatchers: Mutex<HashMap<&'static str, Arc<dyn MessageDispatcherInterface>>>, dispatchers: Mutex<HashMap<&'static str, Arc<dyn MessageDispatcherInterface>>>,
} }
@@ -186,18 +206,19 @@ pub struct MessageSubsystem {
impl MessageSubsystem { impl MessageSubsystem {
/// Create a new message subsystem. /// Create a new message subsystem.
pub fn new() -> Self { pub fn new() -> Self {
MessageSubsystem { dispatchers: Mutex::new(HashMap::new()) } Self { dispatchers: Mutex::new(HashMap::new()) }
} }
/// Add a new dispatcher for specified Message. /// Add a new dispatcher for specified [`Message`].
pub async fn add_dispatch<M: Message>(&self) { pub async fn add_dispatch<M: Message>(&self) {
self.dispatchers.lock().await.insert(M::name(), Arc::new(MessageDispatcher::<M>::new())); self.dispatchers.lock().await.insert(M::NAME, Arc::new(MessageDispatcher::<M>::new()));
} }
/// Subscribes to a Message. Using the Message name, the method returns an the associated MessageDispatcher from the list of /// Subscribes to a [`Message`]. Using the Message name, the method
/// dispatchers and calls subscribe(). /// returns the associated [`MessageDispatcher`] from the list of
/// dispatchers and calls `subscribe()`.
pub async fn subscribe<M: Message>(&self) -> Result<MessageSubscription<M>> { pub async fn subscribe<M: Message>(&self) -> Result<MessageSubscription<M>> {
let dispatcher = self.dispatchers.lock().await.get(M::name()).cloned(); let dispatcher = self.dispatchers.lock().await.get(M::NAME).cloned();
let sub = match dispatcher { let sub = match dispatcher {
Some(dispatcher) => { Some(dispatcher) => {
@@ -208,9 +229,9 @@ impl MessageSubsystem {
dispatcher.subscribe().await dispatcher.subscribe().await
} }
None => { None => {
// normall return failure here // Normal return failure here
// for now panic
return Err(Error::NetworkOperationFailed) return Err(Error::NetworkOperationFailed)
} }
}; };
@@ -218,102 +239,72 @@ impl MessageSubsystem {
Ok(sub) Ok(sub)
} }
/// Transmits a payload to a dispatcher. Returns an error if the payload /// Transmits a payload to a dispatcher.
/// fails to transmit. /// Returns an error if the payload fails to transmit.
pub async fn notify(&self, command: &str, payload: Vec<u8>) { pub async fn notify(&self, command: &str, payload: &[u8]) {
let dispatcher = self.dispatchers.lock().await.get(command).cloned(); let Some(dispatcher) = self.dispatchers.lock().await.get(command).cloned() else {
warn!(
target: "net::message_subscriber::notify",
"message_subscriber::notify: Command '{}' did not find a dispatcher",
command,
);
return
};
match dispatcher { dispatcher.trigger(payload).await;
Some(dispatcher) => {
dispatcher.trigger(payload).await;
}
None => {
warn!(
target: "net::message_subscriber::notify()",
"Command '{}' did not find a dispatcher",
command
);
}
}
} }
/// Transmits an error message across dispatchers. /// Concurrently transmits an error message across dispatchers.
pub async fn trigger_error(&self, err: Error) { pub async fn trigger_error(&self, err: Error) {
// TODO: this could be parallelized let mut futures = FuturesUnordered::new();
for dispatcher in self.dispatchers.lock().await.values() {
dispatcher.trigger_error(err.clone()).await; let dispatchers = self.dispatchers.lock().await;
for dispatcher in dispatchers.values() {
let dispatcher = dispatcher.clone();
let error = err.clone();
futures.push(async move { dispatcher.trigger_error(error).await });
} }
drop(dispatchers);
while let Some(_r) = futures.next().await {}
} }
} }
impl Default for MessageSubsystem {
fn default() -> Self {
Self::new()
}
}
/// Test functions for message subsystem.
// This is a test function for the message subsystem code above
// Normall we would use the #[test] macro but cannot since it is async code
// Instead we call it using smol::block_on() in the unit test code after this
// func
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use darkfi_serial::{Decodable, Encodable}; use darkfi_serial::{serialize, SerialDecodable, SerialEncodable};
use std::io;
#[async_std::test] #[async_std::test]
async fn message_subscriber_test() { async fn message_subscriber_test() {
struct MyVersionMessage { #[derive(SerialEncodable, SerialDecodable)]
x: u32, struct MyVersionMessage(pub u32);
} crate::impl_p2p_message!(MyVersionMessage, "verver");
impl Message for MyVersionMessage {
fn name() -> &'static str {
"verver"
}
}
impl Encodable for MyVersionMessage {
fn encode<S: io::Write>(&self, mut s: S) -> core::result::Result<usize, io::Error> {
let mut len = 0;
len += self.x.encode(&mut s)?;
Ok(len)
}
}
impl Decodable for MyVersionMessage {
fn decode<D: io::Read>(mut d: D) -> core::result::Result<Self, io::Error> {
Ok(Self { x: Decodable::decode(&mut d)? })
}
}
println!("hello");
let subsystem = MessageSubsystem::new(); let subsystem = MessageSubsystem::new();
subsystem.add_dispatch::<MyVersionMessage>().await; subsystem.add_dispatch::<MyVersionMessage>().await;
// subscribe // Subscribe:
// 1. get dispatcher // 1. Get dispatcher
// 2. cast to specific type // 2. Cast to specific type
// 3. do sub, return sub // 3. Do sub, return sub
let sub = subsystem.subscribe::<MyVersionMessage>().await.unwrap(); let sub = subsystem.subscribe::<MyVersionMessage>().await.unwrap();
let msg = MyVersionMessage { x: 110 }; // Receive message and publish:
let mut payload = Vec::new(); // 1. Based on string, lookup relevant dispatcher interface
msg.encode(&mut payload).unwrap(); // 2. Publish data there
let msg = MyVersionMessage(110);
let payload = serialize(&msg);
subsystem.notify("verver", &payload).await;
// receive message and publish // Receive:
// 1. based on string, lookup relevant dispatcher interface // 1. Do a get easy
// 2. publish data there
subsystem.notify("verver", payload).await;
// receive
// 1. do a get easy
let msg2 = sub.receive().await.unwrap(); let msg2 = sub.receive().await.unwrap();
assert_eq!(msg2.x, 110); assert_eq!(msg.0, msg2.0);
println!("{}", msg2.x);
// Trigger an error
subsystem.trigger_error(Error::ChannelStopped).await; subsystem.trigger_error(Error::ChannelStopped).await;
let msg2 = sub.receive().await; let msg2 = sub.receive().await;

View File

@@ -16,30 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/// Acceptor class handles the acceptance of inbound socket connections. It's /// Defines how to decode generic messages as well as implementing the
/// used to start listening on a local socket address, to accept incoming /// common network messages that are sent between nodes as described
/// connections and to handle network errors. /// by the [`protocol`] submodule.
pub mod acceptor;
/// Async channel that handles the sending of messages across the network.
/// Public interface is used to create new channels, to stop and start
/// a channel, and to send messages.
/// ///
/// Implements message functionality and the message subscriber subsystem. /// Implements a type called [`Packet`] which is the base message type.
pub mod channel; /// Packets are converted into messages and passed to an event loop.
pub mod message;
pub use message::Message;
/// Handles the creation of outbound connections. Used to establish an outbound /// Generic publish/subscribe class that can dispatch any kind of message
/// connection. /// to a subscribed list of dispatchers. Dispatchers subscribe to a single
pub mod connector;
/// Hosts are a list of network addresses used when establishing an outbound
/// connection. Hosts are shared across the network through the address
/// protocol. When attempting to connect, a node will loop through addresses in
/// the host store until it finds ones to connect to.
pub mod hosts;
/// Generic publish/subscribe class that can dispatch any kind of message to a
/// subscribed list of dispatchers. Dispatchers subscribe to a single
/// message format of any type. This is a generalized version of the simple /// message format of any type. This is a generalized version of the simple
/// publish-subscribe class in system::Subscriber. /// publish-subscribe class in system::Subscriber.
/// ///
@@ -48,77 +35,85 @@ pub mod hosts;
/// ///
/// Message Subsystem maintains a list of dispatchers, which is a generalized /// Message Subsystem maintains a list of dispatchers, which is a generalized
/// version of a subscriber. Pub-sub is called on dispatchers through the /// version of a subscriber. Pub-sub is called on dispatchers through the
/// functions 'subscribe' and 'notify'. Whereas system::Subscriber only allows /// functions `subscribe` and `notify`. Whereas system::Subscriber only allows
/// messages of a single type, dispatchers can handle any kind of message. This /// messages of a single type, dispatchers can handle any kind of message. This
/// generic message is called a payload and is processed and decoded by the /// generic message is called a payload and is processed and decoded by the
/// Message Dispatcher. /// Message Dispatcher.
/// ///
/// The Message Dispatcher is a class of subscribers that implements a /// The Message Dispatcher is a class of subscribers that implement a generic
/// generic trait called Message Dispatcher Interface, which allows us to /// trait called Message Dispatcher Interface, which allows us to process any
/// process any kind of payload as a message. /// kind of payload as a message.
pub mod message_subscriber; pub mod message_subscriber;
pub use message_subscriber::MessageSubscription;
/// Defines how to decode generic messages as well as implementing the common /// Network transports, holds implementations of pluggable transports.
/// network messages that are sent between nodes as described by the Protocol /// Exposes agnostic dialers and agnostic listeners.
/// submodule.
///
/// Implements a type called Packet which is the base message type. Packets are
/// converted into messages and passed to an event loop.
pub mod message;
/// P2P provides all core functionality to interact with the peer-to-peer
/// network.
///
/// Used to create a network, to start and run it, to broadcast messages across
/// all channels, and to manage the channel store.
///
/// The channel store is a hashmap of channel address that we can use to add and
/// remove channels or check whether a channel is already is in the store.
pub mod p2p;
/// Defines the networking protocol used at each stage in a connection. Consists
/// of a series of messages that are sent across the network at the different
/// connection stages.
///
/// When a node connects to a network for the first time, it must follow a seed
/// protocol, which provides it with a list of network hosts to connect to. To
/// establish a connection to another node, nodes must send version and version
/// acknowledgement messages. During a connection, nodes continually get address
/// and get-address messages to inform eachother about what nodes are on the
/// network. Nodes also send out a ping and pong message which keeps the network
/// from shutting down.
///
/// Protocol submodule also implements a jobs manager than handles the
/// asynchronous execution of the protocols.
pub mod protocol;
/// Defines the interaction between nodes during a connection. Consists of an
/// inbound session, which describes how to set up an incoming connection, and
/// an outbound session, which describes setting up an outbound connection. Also
/// describes the seed session, which is the type of connection used when a node
/// connects to the network for the first time. Implements the session trait
/// which describes the common functions across all sessions.
pub mod session;
/// Network configuration settings.
pub mod settings;
/// Network transport implementations.
pub mod transport; pub mod transport;
/// Network constants for various validations. /// Hosts are a list of network addresses used when establishing outbound
pub mod constants; /// connections. Hosts are shared across the network through the address
/// protocol. When attempting to connect, a node will loop through addresses
/// in the hosts store until it finds ones to connect to.
pub mod hosts;
pub use acceptor::{Acceptor, AcceptorPtr}; /// Async channel that handles the sending of messages across the network.
pub use channel::{Channel, ChannelPtr}; /// Public interface is used to create new channels, to stop and start a
pub use connector::Connector; /// channel, and to send messages.
pub use hosts::{Hosts, HostsPtr}; pub mod channel;
pub use message::Message; pub use channel::ChannelPtr;
pub use message_subscriber::MessageSubscription;
/// P2P provides all core functionality to interact with the P2P network.
///
/// Used to create a network, to start and run it, to broadcast messages
/// across all channels, and to manage the channel store.
///
/// The channel store is a hashmap of channel addresses that we can use
/// to add and remove channels or check whether a channel is already in
/// the store.
#[macro_use]
pub mod p2p;
pub use p2p::{P2p, P2pPtr}; pub use p2p::{P2p, P2pPtr};
pub use protocol::{ProtocolBase, ProtocolBasePtr, ProtocolJobsManager, ProtocolJobsManagerPtr};
pub use session::{ /// Defines the networking protocol used at each stage in a connection.
Session, SessionBitflag, SessionWeakPtr, SESSION_ALL, SESSION_INBOUND, SESSION_MANUAL, /// Consists of a series of messages that are sent across the network at
SESSION_OUTBOUND, SESSION_SEED, /// the different connection stages.
///
/// When a node connects to a network for the first time, it must follow
/// a seed protocol, which provides it with a list of network hosts to
/// connect to. To establish a connection to another node, nodes must send
/// version and version acknowledgement messages. During a connection, nodes
/// continually get address and get-address messages to inform each other
/// about what nodes are on the network. Nodes also send out a ping and pong
/// message which keeps the network from shutting down.
///
/// Protocol submodule also implements a jobs manager that handles the
/// asynchronous execution of the protocols.
pub mod protocol;
pub use protocol::{
protocol_base::{ProtocolBase, ProtocolBasePtr},
protocol_jobs_manager::{ProtocolJobsManager, ProtocolJobsManagerPtr},
}; };
pub use settings::{Settings, SettingsPtr};
/// Defines the interaction between nodes during a connection.
/// Consists of an inbound session, which describes how to set up an
/// incoming connection, and an outbound session, which describes setting
/// up an outbound connection. Also describes the sesd session, which is
/// the type of connection used when a node connects to the network for
/// the first time. Implements the [`Session`] trait which describes the
/// common functions across all sessions.
pub mod session;
pub use session::SESSION_ALL;
/// Handles the acceptance of inbound socket connections.
/// Used to start listening on a local socket, to accept incoming connections,
/// and to handle network errors.
pub mod acceptor;
/// Handles the creation of outbound connections.
/// Used to establish an outbound connection.
pub mod connector;
/// Network configuration settings. This holds the configured P2P instance
/// behaviour and is controlled by clients of this API.
pub mod settings;
pub use settings::Settings;

View File

@@ -16,52 +16,58 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{ use std::collections::{HashMap, HashSet};
collections::{HashMap, HashSet},
fmt,
};
use async_std::sync::{Arc, Mutex}; use async_std::{
use futures::{select, stream::FuturesUnordered, try_join, FutureExt, StreamExt, TryFutureExt}; stream::StreamExt,
use log::{debug, error, warn}; sync::{Arc, Mutex},
use rand::Rng; };
use futures::{stream::FuturesUnordered, TryFutureExt};
use log::{debug, error, info, warn};
use rand::{prelude::IteratorRandom, rngs::OsRng};
use serde_json::json; use serde_json::json;
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use super::{
channel::ChannelPtr,
hosts::{Hosts, HostsPtr},
message::Message,
protocol::{protocol_registry::ProtocolRegistry, register_default_protocols},
session::{
InboundSession, InboundSessionPtr, ManualSession, ManualSessionPtr, OutboundSession,
OutboundSessionPtr, SeedSyncSession, Session,
},
settings::{Settings, SettingsPtr},
};
use crate::{ use crate::{
system::{Subscriber, SubscriberPtr, Subscription}, system::{Subscriber, SubscriberPtr, Subscription},
util::async_util::sleep,
Result, Result,
}; };
use super::{ /// Set of channels that are awaiting connection
message::Message,
protocol::{register_default_protocols, ProtocolRegistry},
session::{InboundSession, ManualSession, OutboundSession, SeedSyncSession, Session},
Channel, ChannelPtr, Hosts, HostsPtr, Settings, SettingsPtr,
};
/// List of channels that are awaiting connection.
pub type PendingChannels = Mutex<HashSet<Url>>; pub type PendingChannels = Mutex<HashSet<Url>>;
/// List of connected channels. /// Set of connected channels
pub type ConnectedChannels = Mutex<HashMap<Url, Arc<Channel>>>; pub type ConnectedChannels = Mutex<HashMap<Url, ChannelPtr>>;
/// Atomic pointer to p2p interface. /// Atomic pointer to the p2p interface
pub type P2pPtr = Arc<P2p>; pub type P2pPtr = Arc<P2p>;
/// Representations of the p2p state
enum P2pState { enum P2pState {
// The p2p object has been created but not yet started. /// The P2P object has been created but not yet started
Open, Open,
// We are performing the initial seed session /// We are performing the initial seed session
Start, Start,
// Seed session finished, but not yet running /// Seed session finished, but not yet running
Started, Started,
// p2p is running and the network is active. /// P2P is running and the network is active
Run, Run,
/// The P2P network has been stopped
Stopped,
} }
impl fmt::Display for P2pState { impl std::fmt::Display for P2pState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!( write!(
f, f,
"{}", "{}",
@@ -70,43 +76,54 @@ impl fmt::Display for P2pState {
Self::Start => "start", Self::Start => "start",
Self::Started => "started", Self::Started => "started",
Self::Run => "run", Self::Run => "run",
Self::Stopped => "stopped",
} }
) )
} }
} }
/// Top level peer-to-peer networking interface. /// Toplevel peer-to-peer networking interface
pub struct P2p { pub struct P2p {
/// Channels pending connection
pending: PendingChannels, pending: PendingChannels,
/// Connected channels
channels: ConnectedChannels, channels: ConnectedChannels,
/// Subscriber for notifications of new channels
channel_subscriber: SubscriberPtr<Result<ChannelPtr>>, channel_subscriber: SubscriberPtr<Result<ChannelPtr>>,
// Used both internally and externally /// Subscriber for stop notifications
stop_subscriber: SubscriberPtr<()>, stop_subscriber: SubscriberPtr<()>,
/// Known hosts (peers)
hosts: HostsPtr, hosts: HostsPtr,
/// Protocol registry
protocol_registry: ProtocolRegistry, protocol_registry: ProtocolRegistry,
/// The state of the interface
state: Mutex<P2pState>,
/// P2P network settings
settings: SettingsPtr,
/// Boolean lock marking if peer discovery is active
pub peer_discovery_running: Mutex<bool>,
// We keep a reference to the sessions used for get info /// Reference to configured [`ManualSession`]
session_manual: Mutex<Option<Arc<ManualSession>>>, session_manual: Mutex<Option<Arc<ManualSession>>>,
/// Reference to configured [`InboundSession`]
session_inbound: Mutex<Option<Arc<InboundSession>>>, session_inbound: Mutex<Option<Arc<InboundSession>>>,
/// Reference to configured [`OutboundSession`]
session_outbound: Mutex<Option<Arc<OutboundSession>>>, session_outbound: Mutex<Option<Arc<OutboundSession>>>,
state: Mutex<P2pState>, /// Enable network debugging
pub dnet_enabled: Mutex<bool>,
settings: SettingsPtr,
/// Flag to check if on discovery mode
discovery: Mutex<bool>,
} }
impl P2p { impl P2p {
/// Initialize a new p2p network. /// Initialize a new p2p network.
/// ///
/// Initializes all sessions and protocols. Adds the protocols to the protocol registry, along /// Initializes all sessions and protocols. Adds the protocols to the protocol
/// with a bitflag session selector that includes or excludes sessions from seed, version, and /// registry, along with a bitflag session selector that includes or excludes
/// address protocols. /// sessions from seed, version, and address protocols.
/// ///
/// Creates a weak pointer to self that is used by all sessions to access the p2p parent class. /// Creates a weak pointer to self that is used by all sessions to access the
pub async fn new(settings: Settings) -> Arc<Self> { /// p2p parent class.
pub async fn new(settings: Settings) -> P2pPtr {
let settings = Arc::new(settings); let settings = Arc::new(settings);
let self_ = Arc::new(Self { let self_ = Arc::new(Self {
@@ -114,20 +131,23 @@ impl P2p {
channels: Mutex::new(HashMap::new()), channels: Mutex::new(HashMap::new()),
channel_subscriber: Subscriber::new(), channel_subscriber: Subscriber::new(),
stop_subscriber: Subscriber::new(), stop_subscriber: Subscriber::new(),
hosts: Hosts::new(settings.localnet), hosts: Hosts::new(settings.clone()),
protocol_registry: ProtocolRegistry::new(), protocol_registry: ProtocolRegistry::new(),
state: Mutex::new(P2pState::Open),
settings,
peer_discovery_running: Mutex::new(false),
session_manual: Mutex::new(None), session_manual: Mutex::new(None),
session_inbound: Mutex::new(None), session_inbound: Mutex::new(None),
session_outbound: Mutex::new(None), session_outbound: Mutex::new(None),
state: Mutex::new(P2pState::Open),
settings, dnet_enabled: Mutex::new(false),
discovery: Mutex::new(false),
}); });
let parent = Arc::downgrade(&self_); let parent = Arc::downgrade(&self_);
*self_.session_manual.lock().await = Some(ManualSession::new(parent.clone())); *self_.session_manual.lock().await = Some(ManualSession::new(parent.clone()));
*self_.session_inbound.lock().await = Some(InboundSession::new(parent.clone()).await); *self_.session_inbound.lock().await = Some(InboundSession::new(parent.clone()));
*self_.session_outbound.lock().await = Some(OutboundSession::new(parent)); *self_.session_outbound.lock().await = Some(OutboundSession::new(parent));
register_default_protocols(self_.clone()).await; register_default_protocols(self_.clone()).await;
@@ -135,375 +155,213 @@ impl P2p {
self_ self_
} }
// ANCHOR: get_info
pub async fn get_info(&self) -> serde_json::Value { pub async fn get_info(&self) -> serde_json::Value {
// Building ext_addr_vec string let mut ext = vec![];
let mut ext_addr_vec = vec![]; for addr in &self.settings.external_addrs {
for ext_addr in &self.settings.external_addr { ext.push(addr.to_string());
ext_addr_vec.push(ext_addr.as_ref().to_string());
} }
json!({ json!({
"external_addr": format!("{:?}", ext_addr_vec), "external_addrs": format!("{:?}", ext),
"session_manual": self.session_manual().await.get_info().await, //"session_manual": self.session_manual().await.get_info().await,
"session_inbound": self.session_inbound().await.get_info().await, "session_inbound": self.session_inbound().await.get_info().await,
"session_outbound": self.session_outbound().await.get_info().await, "session_outbound": self.session_outbound().await.get_info().await,
"state": self.state.lock().await.to_string(), "state": self.state.lock().await.to_string(),
}) })
} }
// ANCHOR_END: get_info
/// Invoke startup and seeding sequence. Call from constructing thread. /// Invoke startup and seeding sequence. Call from constructing thread.
// ANCHOR: start pub async fn start(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
pub async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { debug!(target: "net::p2p::start()", "P2P::start() [BEGIN]");
debug!(target: "net::p2p::start()", "P2p::start() [BEGIN]"); info!(target: "net::p2p::start()", "[P2P] Seeding P2P subsystem");
*self.state.lock().await = P2pState::Start; *self.state.lock().await = P2pState::Start;
// Start seed session // Start seed session
let seed = SeedSyncSession::new(Arc::downgrade(&self)); let seed = SeedSyncSession::new(Arc::downgrade(&self));
// This will block until all seed queries have finished // This will block until all seed queries have finished
seed.start(executor.clone()).await?; seed.start(ex.clone()).await?;
*self.state.lock().await = P2pState::Started; *self.state.lock().await = P2pState::Started;
debug!(target: "net::p2p::start()", "P2p::start() [END]"); debug!(target: "net::p2p::start()", "P2P::start() [END]");
Ok(()) Ok(())
} }
// ANCHOR_END: start
pub async fn session_manual(&self) -> Arc<ManualSession> { /// Runs the network. Starts inbound, outbound, and manual sessions.
self.session_manual.lock().await.as_ref().unwrap().clone()
}
pub async fn session_inbound(&self) -> Arc<InboundSession> {
self.session_inbound.lock().await.as_ref().unwrap().clone()
}
pub async fn session_outbound(&self) -> Arc<OutboundSession> {
self.session_outbound.lock().await.as_ref().unwrap().clone()
}
/// Runs the network. Starts inbound, outbound and manual sessions.
/// Waits for a stop signal and stops the network if received. /// Waits for a stop signal and stops the network if received.
// ANCHOR: run pub async fn run(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
pub async fn run(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { debug!(target: "net::p2p::run()", "P2P::run() [BEGIN]");
debug!(target: "net::p2p::run()", "P2p::run() [BEGIN]"); info!(target: "net::p2p::run()", "[P2P] Running P2P subsystem");
*self.state.lock().await = P2pState::Run; *self.state.lock().await = P2pState::Run;
// First attempt any set manual connections
let manual = self.session_manual().await; let manual = self.session_manual().await;
for peer in &self.settings.peers { for peer in &self.settings.peers {
manual.clone().connect(peer, executor.clone()).await; manual.clone().connect(peer.clone(), ex.clone()).await;
} }
// Start the inbound session
let inbound = self.session_inbound().await; let inbound = self.session_inbound().await;
inbound.clone().start(executor.clone()).await?; inbound.clone().start(ex.clone()).await?;
// Start the outbound session
let outbound = self.session_outbound().await; let outbound = self.session_outbound().await;
outbound.clone().start(executor.clone()).await?; outbound.clone().start(ex.clone()).await?;
info!(target: "net::p2p::run()", "[P2P] P2P subsystem started");
let stop_sub = self.subscribe_stop().await;
// Wait for stop signal // Wait for stop signal
let stop_sub = self.subscribe_stop().await;
stop_sub.receive().await; stop_sub.receive().await;
info!(target: "net::p2p::run()", "[P2P] Received P2P subsystem stop signal. Shutting down.");
// Stop the sessions // Stop the sessions
manual.stop().await; manual.stop().await;
inbound.stop().await; inbound.stop().await;
outbound.stop().await; outbound.stop().await;
debug!(target: "net::p2p::run()", "P2p::run() [END]"); *self.state.lock().await = P2pState::Stopped;
debug!(target: "net::p2p::run()", "P2P::run() [END]");
Ok(()) Ok(())
} }
// ANCHOR_END: run
/// Wait for outbound connections to be established.
pub async fn wait_for_outbound(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> {
debug!(target: "net::p2p::wait_for_outbound()", "P2p::wait_for_outbound() [BEGIN]");
// To verify that the network needs initialization, we check if we have seeds or peers configured,
// and have configured outbound slots.
if !(self.settings.seeds.is_empty() && self.settings.peers.is_empty()) &&
self.settings.outbound_connections > 0
{
debug!(target: "net::p2p::wait_for_outbound()", "P2p::wait_for_outbound(): seeds are configured, waiting for outbound initialization...");
// Retrieve P2P network settings;
let settings = self.settings();
// Retrieve our own inbound addresses
let self_inbound_addr = &settings.external_addr;
// Retrieve timeout config
let timeout = settings.connect_timeout_seconds as u64;
// Retrieve outbound addresses to connect to (including manual peers)
let peers = &settings.peers;
let outbound = &self.hosts().load_all().await;
// Enable manual channel subscriber notifications
self.session_manual().await.clone().enable_notify().await;
// Retrieve manual channel subscriber ptr
let manual_sub =
self.session_manual.lock().await.as_ref().unwrap().subscribe_channel().await;
// Enable outbound channel subscriber notifications
self.session_outbound().await.clone().enable_notify().await;
// Retrieve outbound channel subscriber ptr
let outbound_sub =
self.session_outbound.lock().await.as_ref().unwrap().subscribe_channel().await;
// Create tasks for peers and outbound
let peers_task = Self::outbound_addr_loop(
self_inbound_addr,
timeout,
self.subscribe_stop().await,
peers,
manual_sub,
executor.clone(),
);
let outbound_task = Self::outbound_addr_loop(
self_inbound_addr,
timeout,
self.subscribe_stop().await,
outbound,
outbound_sub,
executor,
);
// Wait for both tasks completion
try_join!(peers_task, outbound_task)?;
// Disable manual channel subscriber notifications
self.session_manual().await.disable_notify().await;
// Disable outbound channel subscriber notifications
self.session_outbound().await.disable_notify().await;
}
debug!(target: "net::p2p::wait_for_outbound()", "P2p::wait_for_outbound() [END]");
Ok(())
}
// Wait for the process for each of the provided addresses, excluding our own inbound addresses
async fn outbound_addr_loop(
self_inbound_addr: &[Url],
timeout: u64,
stop_sub: Subscription<()>,
addrs: &Vec<Url>,
subscriber: Subscription<Result<ChannelPtr>>,
executor: Arc<Executor<'_>>,
) -> Result<()> {
// Process addresses
for addr in addrs {
if self_inbound_addr.contains(addr) {
continue
}
// Wait for address to be processed.
// We use a timeout to eliminate the following cases:
// 1. Network timeout
// 2. Thread reaching the receiver after peer has signal it
let (timeout_s, timeout_r) = smol::channel::unbounded::<()>();
executor
.spawn(async move {
sleep(timeout).await;
timeout_s.send(()).await.unwrap_or(());
})
.detach();
select! {
msg = subscriber.receive().fuse() => {
if let Err(e) = msg {
warn!(
target: "net::p2p::outbound_addr_loop()",
"P2p::wait_for_outbound(): Outbound connection failed [{}]: {}",
addr, e
);
}
},
_ = stop_sub.receive().fuse() => debug!(target: "net::p2p::outbound_addr_loop()", "P2p::wait_for_outbound(): stop signal received!"),
_ = timeout_r.recv().fuse() => {
warn!(target: "net::p2p::outbound_addr_loop()", "P2p::wait_for_outbound(): Timeout on outbound connection: {}", addr);
continue
},
}
}
Ok(())
}
// ANCHOR: stop
pub async fn stop(&self) {
self.stop_subscriber.notify(()).await
}
// ANCHOR_END: stop
/// Broadcasts a message concurrently across all channels.
// ANCHOR: broadcast
pub async fn broadcast<M: Message + Clone>(&self, message: M) -> Result<()> {
let chans = self.channels.lock().await;
let iter = chans.values();
let mut futures = FuturesUnordered::new();
for channel in iter {
futures.push(channel.send(message.clone()).map_err(|e| {
format!(
"P2P::broadcast: Broadcasting message to {} failed: {}",
channel.address(),
e
)
}));
}
if futures.is_empty() {
error!(target: "net::p2p::broadcast()", "P2P::broadcast: No connected channels found");
return Ok(())
}
while let Some(entry) = futures.next().await {
if let Err(e) = entry {
error!(target: "net::p2p::broadcast()", "{}", e);
}
}
Ok(())
}
// ANCHOR_END: broadcast
/// Broadcasts a message concurrently across all channels.
/// Excludes channels provided in `exclude_list`.
pub async fn broadcast_with_exclude<M: Message + Clone>(
&self,
message: M,
exclude_list: &[Url],
) -> Result<()> {
let chans = self.channels.lock().await;
let iter = chans.values();
let mut futures = FuturesUnordered::new();
for channel in iter {
if !exclude_list.contains(&channel.address()) {
futures.push(channel.send(message.clone()).map_err(|e| {
format!(
"P2P::broadcast_with_exclude: Broadcasting message to {} failed: {}",
channel.address(),
e
)
}));
}
}
if futures.is_empty() {
error!(target: "net::p2p::broadcast_with_exclude()", "P2P::broadcast_with_exclude: No connected channels found");
return Ok(())
}
while let Some(entry) = futures.next().await {
if let Err(e) = entry {
error!(target: "net::p2p::broadcast_with_exclude()", "{}", e);
}
}
Ok(())
}
/// Add channel address to the list of connected channels.
pub async fn store(&self, channel: ChannelPtr) {
self.channels.lock().await.insert(channel.address(), channel.clone());
self.channel_subscriber.notify(Ok(channel)).await;
}
/// Remove a channel from the list of connected channels.
pub async fn remove(&self, channel: ChannelPtr) {
self.channels.lock().await.remove(&channel.address());
}
/// Check whether a channel is stored in the list of connected channels.
/// If key is not contained, we also check if we are connected with a different transport.
pub async fn exists(&self, addr: &Url) -> Result<bool> {
let channels = self.channels.lock().await;
if channels.contains_key(addr) {
return Ok(true)
}
let mut addr = addr.clone();
for transport in &self.settings.outbound_transports {
addr.set_scheme(&transport.to_scheme())?;
if channels.contains_key(&addr) {
return Ok(true)
}
}
Ok(false)
}
/// Add a channel to the list of pending channels.
pub async fn add_pending(&self, addr: Url) -> bool {
self.pending.lock().await.insert(addr)
}
/// Remove a channel from the list of pending channels.
pub async fn remove_pending(&self, addr: &Url) {
self.pending.lock().await.remove(addr);
}
/// Return the number of connected channels.
pub async fn connections_count(&self) -> usize {
self.channels.lock().await.len()
}
/// Return an atomic pointer to the default network settings.
pub fn settings(&self) -> SettingsPtr {
self.settings.clone()
}
/// Return an atomic pointer to the list of hosts.
pub fn hosts(&self) -> HostsPtr {
self.hosts.clone()
}
pub fn protocol_registry(&self) -> &ProtocolRegistry {
&self.protocol_registry
}
/// Subscribe to a channel.
pub async fn subscribe_channel(&self) -> Subscription<Result<ChannelPtr>> {
self.channel_subscriber.clone().subscribe().await
}
/// Subscribe to a stop signal. /// Subscribe to a stop signal.
pub async fn subscribe_stop(&self) -> Subscription<()> { pub async fn subscribe_stop(&self) -> Subscription<()> {
self.stop_subscriber.clone().subscribe().await self.stop_subscriber.clone().subscribe().await
} }
/// Retrieve channels /// Stop the running P2P subsystem
pub async fn stop(&self) {
self.stop_subscriber.notify(()).await
}
/// Add a channel to the set of connected channels
pub async fn store(&self, channel: ChannelPtr) {
// TODO: Check the code path for this, and potentially also insert the remote
// into the hosts list?
self.channels.lock().await.insert(channel.address().clone(), channel.clone());
self.channel_subscriber.notify(Ok(channel)).await;
}
/// Broadcasts a message concurrently across all active channels.
pub async fn broadcast<M: Message>(&self, message: &M) {
self.broadcast_with_exclude(message, &[]).await
}
/// Broadcasts a message concurrently across active channels, excluding
/// the ones provided in `exclude_list`.
pub async fn broadcast_with_exclude<M: Message>(&self, message: &M, exclude_list: &[Url]) {
let chans = self.channels.lock().await;
let iter = chans.values();
let mut futures = FuturesUnordered::new();
for channel in iter {
if exclude_list.contains(channel.address()) {
continue
}
futures.push(channel.send(message).map_err(|e| {
format!("P2P: Broadcasting message to {} failed: {}", channel.address(), e)
}));
}
if futures.is_empty() {
warn!(target: "net::p2p::broadcast()", "P2P: No connected channels found for broadcast");
return
}
while let Some(entry) = futures.next().await {
// TODO: Here we can close the channels.
// See message_subscriber::_trigger_all on how to do it.
if let Err(e) = entry {
error!(target: "net::p2p::broadcast()", "{}", e);
}
}
}
/// Check whether we're connected to a given address
pub async fn exists(&self, addr: &Url) -> bool {
self.channels.lock().await.contains_key(addr)
}
/// Remove a channel from the set of connected channels
pub async fn remove(&self, channel: ChannelPtr) {
self.channels.lock().await.remove(channel.address());
}
/// Add an address to the list of pending channels.
pub async fn add_pending(&self, addr: &Url) -> bool {
self.pending.lock().await.insert(addr.clone())
}
/// Remove a channel from the list of pending channels.
pub async fn remove_pending(&self, addr: &Url) {
self.pending.lock().await.remove(addr);
}
/// Return reference to connected channels map
pub fn channels(&self) -> &ConnectedChannels { pub fn channels(&self) -> &ConnectedChannels {
&self.channels &self.channels
} }
/// Try to start discovery mode. /// Retrieve a random connected channel from the
/// Returns false if already on discovery mode. pub async fn random_channel(&self) -> Option<ChannelPtr> {
pub async fn start_discovery(self: Arc<Self>) -> bool { let channels = self.channels().lock().await;
if *self.discovery.lock().await { channels.values().choose(&mut OsRng).cloned()
return false
}
*self.discovery.lock().await = true;
true
} }
/// Stops discovery mode. /// Return an atomic pointer to the set network settings
pub async fn stop_discovery(self: Arc<Self>) { pub fn settings(&self) -> SettingsPtr {
*self.discovery.lock().await = false; self.settings.clone()
} }
/// Retrieves a random connected channel, exluding seeds /// Return an atomic pointer to the list of hosts
pub async fn random_channel(self: Arc<Self>) -> Option<Arc<Channel>> { pub fn hosts(&self) -> HostsPtr {
let mut channels_map = self.channels().lock().await.clone(); self.hosts.clone()
channels_map.retain(|c, _| !self.settings.seeds.contains(c)); }
let mut values = channels_map.values();
if values.len() == 0 { /// Return a reference to the internal protocol registry
return None pub fn protocol_registry(&self) -> &ProtocolRegistry {
} &self.protocol_registry
}
Some(values.nth(rand::thread_rng().gen_range(0..values.len())).unwrap().clone()) /// Get pointer to manual session
pub async fn session_manual(&self) -> ManualSessionPtr {
self.session_manual.lock().await.as_ref().unwrap().clone()
}
/// Get pointer to inbound session
pub async fn session_inbound(&self) -> InboundSessionPtr {
self.session_inbound.lock().await.as_ref().unwrap().clone()
}
/// Get pointer to outbound session
pub async fn session_outbound(&self) -> OutboundSessionPtr {
self.session_outbound.lock().await.as_ref().unwrap().clone()
}
/// Enable network debugging
pub async fn enable_dnet(&self) {
*self.dnet_enabled.lock().await = true;
warn!("[P2P] Network debugging enabled!");
}
/// Disable network debugging
pub async fn disable_dnet(&self) {
*self.dnet_enabled.lock().await = false;
warn!("[P2P] Network debugging disabled!");
} }
} }
macro_rules! dnet {
($self:expr, $($code:tt)*) => {
{
if *$self.p2p().dnet_enabled.lock().await {
$($code)*
}
}
};
}
pub(crate) use dnet;

View File

@@ -16,71 +16,63 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/// Protocol for address and get-address messages. Implements how nodes exchange
/// connection information about other nodes on the network. Address and
/// get-address messages are exchanged continually alongside ping-pong messages
/// as part of a network connection.
///
/// Protocol starts by creating a subscription to address and get address
/// messages. Then the protocol sends out a get address message and waits for an
/// address message. Upon receiving an address messages, nodes add the
/// address information to their local store.
pub mod protocol_address;
/// Manages the tasks for the network protocol. Used by other connection
/// protocols to handle asynchronous task execution across the network. Runs all
/// tasks that are handed to it on an executor that has stopping functionality.
pub mod protocol_jobs_manager;
/// Protocol for ping-pong keep-alive messages. Implements ping message and pong
/// response. These messages are like the network heartbeat- they are sent
/// continually between nodes, to ensure each node is still alive and active.
/// Ping-pong messages ensure that the network doesn't
/// time out.
///
/// Protocol starts by creating a subscription to ping and pong messages. Then
/// it starts a loop with a timer and runs ping-pong in the task manager. It
/// sends out a ping and waits for pong reply. Then waits for ping and replies
/// with a pong.
pub mod protocol_ping;
/// Seed server protocol. Seed server is used when connecting to the network for
/// the first time. Returns a list of IP addresses that nodes can connect to.
///
/// To start the seed protocol, we create a subscription to the address message,
/// and send our address to the seed server. Then we send a get-address message
/// and receive an address message. We add these addresses to our internal
/// store.
pub mod protocol_seed;
/// Protocol for version information handshake between nodes at the start of a
/// connection. Implements the process for exchanging version information
/// between nodes. This is the first step when establishing a p2p connection.
///
/// The version protocol starts of by instantiating the protocol and creating a
/// new subscription to version and version acknowledgement messages. Then we
/// run the protocol. Nodes send a version message and wait for a version
/// acknowledgement, while asynchronously waiting for version info from the
/// other node and sending the version acknowledgement.
pub mod protocol_version;
pub mod protocol_base;
pub mod protocol_registry;
pub use protocol_address::ProtocolAddress;
pub use protocol_jobs_manager::{ProtocolJobsManager, ProtocolJobsManagerPtr};
pub use protocol_ping::ProtocolPing;
pub use protocol_seed::ProtocolSeed;
pub use protocol_version::ProtocolVersion;
pub use protocol_base::{ProtocolBase, ProtocolBasePtr};
pub use protocol_registry::ProtocolRegistry;
use super::{ use super::{
p2p::P2pPtr,
session::{SESSION_ALL, SESSION_SEED}, session::{SESSION_ALL, SESSION_SEED},
P2pPtr,
}; };
/// Manages the tasks for the network protocol. Used by other connection
/// protocols to handle asynchronous task execution across the network.
/// Runs all tasks that are handed to it on an executor that has stopping
/// functionality.
pub mod protocol_jobs_manager;
/// Protocol for version information handshake between nodes at the start
/// of a connection. This is the first step when establishing a p2p conn.
///
/// The version protocol starts by instantiating the protocol and creating
/// a new subscription to version and version acknowledgement messages.
/// Then we run the protocol. Nodes send a version message and wait for a
/// version acknowledgement, while asynchronously waiting for version info
/// from the other node and sending the version acknowledgement.
pub mod protocol_version;
pub use protocol_version::ProtocolVersion;
/// Protocol for ping-pong keepalive messages. Implements ping message and
/// pong response. These messages are like the network heartbeat - they are
/// sent continually between nodes, to ensure each node is still alive and
/// active. Ping-pong messages ensure that the network doesn't time out.
pub mod protocol_ping;
pub use protocol_ping::ProtocolPing;
/// Protocol for address and get-address messages. Implements how nodes
/// exchange connection information about other nodes on the network.
/// Address and get-address messages are exchanged continually alongside
/// ping-pong messages as part of a network connection.
///
/// Protocol starts by creating a subscription to address and get-address
/// messages. Then the protocol sends out a get-address message and waits
/// for an address message. Upon receiving address messages, nodes validate
/// and add the address information to their local store.
pub mod protocol_address;
pub use protocol_address::ProtocolAddress;
/// Seed servere protocol. Seed server is used when connecting to the network
/// for the first time. Returns a list of peers that nodes can connect to.
///
/// To start the seed protocol, we create a subscription to the address
/// message, and send our address to the seed server. Then we send a
/// get-address message and receive an address message. We add these addresses
/// to our internal store.
pub mod protocol_seed;
pub use protocol_seed::ProtocolSeed;
/// Base trait for implementing P2P protocols
pub mod protocol_base;
/// Interface for registering arbitrary P2P protocols
pub mod protocol_registry;
/// Register the default network protocols for a p2p instance.
pub async fn register_default_protocols(p2p: P2pPtr) { pub async fn register_default_protocols(p2p: P2pPtr) {
let registry = p2p.protocol_registry(); let registry = p2p.protocol_registry();
registry.register(SESSION_ALL, ProtocolPing::init).await; registry.register(SESSION_ALL, ProtocolPing::init).await;

View File

@@ -16,143 +16,125 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::Arc; use async_std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use log::debug; use log::debug;
use rand::seq::SliceRandom;
use smol::Executor; use smol::Executor;
use crate::{util::async_util, Result};
use super::{ use super::{
super::{ super::{
message, message_subscriber::MessageSubscription, ChannelPtr, HostsPtr, P2pPtr, channel::ChannelPtr,
SettingsPtr, SESSION_OUTBOUND, hosts::HostsPtr,
message::{AddrsMessage, GetAddrsMessage},
message_subscriber::MessageSubscription,
p2p::P2pPtr,
session::SESSION_OUTBOUND,
settings::SettingsPtr,
}, },
ProtocolBase, ProtocolBasePtr, ProtocolJobsManager, ProtocolJobsManagerPtr, protocol_base::{ProtocolBase, ProtocolBasePtr},
protocol_jobs_manager::{ProtocolJobsManager, ProtocolJobsManagerPtr},
}; };
use crate::{util::async_util::sleep, Result};
const SEND_ADDR_SLEEP_SECONDS: u64 = 900; /// Defines address and get-address messages
/// Defines address and get-address messages.
pub struct ProtocolAddress { pub struct ProtocolAddress {
channel: ChannelPtr, channel: ChannelPtr,
addrs_sub: MessageSubscription<message::AddrsMessage>, addrs_sub: MessageSubscription<AddrsMessage>,
ext_addrs_sub: MessageSubscription<message::ExtAddrsMessage>, get_addrs_sub: MessageSubscription<GetAddrsMessage>,
get_addrs_sub: MessageSubscription<message::GetAddrsMessage>,
hosts: HostsPtr, hosts: HostsPtr,
jobsman: ProtocolJobsManagerPtr,
settings: SettingsPtr, settings: SettingsPtr,
jobsman: ProtocolJobsManagerPtr,
} }
const PROTO_NAME: &str = "ProtocolAddress";
impl ProtocolAddress { impl ProtocolAddress {
/// Create a new address protocol. Makes an address, an external address /// Creates a new address protocol. Makes an address, an external address
/// and a get-address subscription and adds them to the address protocol instance. /// and a get-address subscription and adds them to the address protocol
/// instance.
pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr { pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr {
let settings = p2p.settings(); let settings = p2p.settings();
let hosts = p2p.hosts(); let hosts = p2p.hosts();
// Creates a subscription to address message. // Creates a subscription to address message
let addrs_sub = channel let addrs_sub =
.clone() channel.subscribe_msg::<AddrsMessage>().await.expect("Missing addrs dispatcher!");
.subscribe_msg::<message::AddrsMessage>()
.await
.expect("Missing addrs dispatcher!");
// Creates a subscription to external address message. // Creates a subscription to get-address message
let ext_addrs_sub = channel let get_addrs_sub =
.clone() channel.subscribe_msg::<GetAddrsMessage>().await.expect("Missing getaddrs dispatcher!");
.subscribe_msg::<message::ExtAddrsMessage>()
.await
.expect("Missing ext_addrs dispatcher!");
// Creates a subscription to get-address message.
let get_addrs_sub = channel
.clone()
.subscribe_msg::<message::GetAddrsMessage>()
.await
.expect("Missing getaddrs dispatcher!");
Arc::new(Self { Arc::new(Self {
channel: channel.clone(), channel: channel.clone(),
addrs_sub, addrs_sub,
ext_addrs_sub,
get_addrs_sub, get_addrs_sub,
hosts, hosts,
jobsman: ProtocolJobsManager::new("ProtocolAddress", channel), jobsman: ProtocolJobsManager::new(PROTO_NAME, channel),
settings, settings,
}) })
} }
/// Handles receiving the address message. Loops to continually recieve /// Handles receiving the address message. Loops to continually receive
/// address messages on the address subsciption. Adds the recieved /// address messages on the address subscription. Validates and adds the
/// addresses to the list of hosts. /// received addresses to the hosts set.
async fn handle_receive_addrs(self: Arc<Self>) -> Result<()> { async fn handle_receive_addrs(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_address::handle_receive_addrs()", "START"); debug!(
target: "net::protocol_address::handle_receive_addrs()",
"[START] address={}", self.channel.address(),
);
loop { loop {
let addrs_msg = self.addrs_sub.receive().await?; let addrs_msg = self.addrs_sub.receive().await?;
debug!( debug!(
target: "net::protocol_address::handle_receive_addrs()", target: "net::protocol_address::handle_receive_addrs()",
"received {} addrs", "Received {} addrs from {}", addrs_msg.addrs.len(), self.channel.address(),
addrs_msg.addrs.len()
); );
self.hosts.store(addrs_msg.addrs.clone()).await;
// TODO: We might want to close the channel here if we're getting
// corrupted addresses.
self.hosts.store(&addrs_msg.addrs).await;
} }
} }
/// Handles receiving the external address message. Loops to continually recieve /// Handles receiving the get-address message. Continually receives get-address
/// external address messages on the address subsciption. Adds the recieved /// messages on the get-address subscription. Then replies with an address message.
/// external addresses to the list of hosts.
async fn handle_receive_ext_addrs(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_address::handle_receive_ext_addrs()", "START");
loop {
let ext_addrs_msg = self.ext_addrs_sub.receive().await?;
debug!(
target: "net::protocol_address::handle_receive_ext_addrs()",
"ProtocolAddress::handle_receive_ext_addrs() received {} addrs",
ext_addrs_msg.ext_addrs.len()
);
self.hosts.store_ext(self.channel.address(), ext_addrs_msg.ext_addrs.clone()).await;
}
}
/// Handles receiving the get-address message. Continually recieves
/// get-address messages on the get-address subsciption. Then replies
/// with an address message.
async fn handle_receive_get_addrs(self: Arc<Self>) -> Result<()> { async fn handle_receive_get_addrs(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_address::handle_receive_get_addrs()", "START"); debug!(
target: "net::protocol_address::handle_receive_get_addrs()",
"[START] address={}", self.channel.address(),
);
loop { loop {
let _get_addrs = self.get_addrs_sub.receive().await?; let get_addrs_msg = self.get_addrs_sub.receive().await?;
debug!( debug!(
target: "net::protocol_address::handle_receive_get_addrs()", target: "net::protocol_address::handle_receive_get_addrs()",
"Received GetAddrs message" "Received GetAddrs({}) message from {}", get_addrs_msg.max, self.channel.address(),
); );
// Loads the list of hosts. let addrs = self.hosts.get_n_random(get_addrs_msg.max).await;
let mut addrs = self.hosts.load_all().await;
// Shuffling list of hosts
addrs.shuffle(&mut rand::thread_rng());
debug!( debug!(
target: "net::protocol_address::handle_receive_get_addrs()", target: "net::protocol_address::handle_receive_get_addrs()",
"Sending {} addrs", "Sending {} addresses to {}", addrs.len(), self.channel.address(),
addrs.len()
); );
// Creates an address messages containing host address.
let addrs_msg = message::AddrsMessage { addrs }; let addrs_msg = AddrsMessage { addrs };
// Sends the address message across the channel. self.channel.send(&addrs_msg).await?;
self.channel.clone().send(addrs_msg).await?;
} }
} }
/// Periodically send our external addresses through the channel.
async fn send_my_addrs(self: Arc<Self>) -> Result<()> { async fn send_my_addrs(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_address::send_my_addrs()", "START"); debug!(
target: "net::protocol_address::send_my_addrs()",
"[START] address={}", self.channel.address(),
);
// FIXME: Revisit this. Why do we keep sending it?
loop { loop {
let ext_addrs = self.settings.external_addr.clone(); let ext_addr_msg = AddrsMessage { addrs: self.settings.external_addrs.clone() };
let ext_addr_msg = message::ExtAddrsMessage { ext_addrs }; self.channel.send(&ext_addr_msg).await?;
self.channel.clone().send(ext_addr_msg).await?; sleep(900).await;
async_util::sleep(SEND_ADDR_SLEEP_SECONDS).await;
} }
} }
} }
@@ -160,32 +142,36 @@ impl ProtocolAddress {
#[async_trait] #[async_trait]
impl ProtocolBase for ProtocolAddress { impl ProtocolBase for ProtocolAddress {
/// Starts the address protocol. Runs receive address and get address /// Starts the address protocol. Runs receive address and get address
/// protocols on the protocol task manager. Then sends get-address /// protocols on the protocol task manager. Then sends get-address msg.
/// message. async fn start(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { debug!(target: "net::protocol_address::start()", "START => address={}", self.channel.address());
let type_id = self.channel.session_type_id(); let type_id = self.channel.session_type_id();
// if it's an outbound session + has an external address let mut jobsman_started = false;
// send our address
if type_id == SESSION_OUTBOUND && !self.settings.external_addr.is_empty() { // If it's an outbound session + has an extern_addr, send our address.
self.jobsman.clone().start(executor.clone()); if type_id == SESSION_OUTBOUND && !self.settings.external_addrs.is_empty() {
self.jobsman.clone().spawn(self.clone().send_my_addrs(), executor.clone()).await; self.jobsman.clone().start(ex.clone());
jobsman_started = true;
self.jobsman.clone().spawn(self.clone().send_my_addrs(), ex.clone()).await;
} }
debug!(target: "net::protocol_address::start()", "START"); if !jobsman_started {
self.jobsman.clone().start(executor.clone()); self.jobsman.clone().start(ex.clone());
self.jobsman.clone().spawn(self.clone().handle_receive_addrs(), executor.clone()).await; }
self.jobsman.clone().spawn(self.clone().handle_receive_ext_addrs(), executor.clone()).await; self.jobsman.clone().spawn(self.clone().handle_receive_addrs(), ex.clone()).await;
self.jobsman.clone().spawn(self.clone().handle_receive_get_addrs(), executor).await; self.jobsman.spawn(self.clone().handle_receive_get_addrs(), ex).await;
// Send get_address message. // Send get_address message.
let get_addrs = message::GetAddrsMessage {}; let get_addrs = GetAddrsMessage { max: self.settings.outbound_connections as u32 };
let _ = self.channel.clone().send(get_addrs).await; self.channel.send(&get_addrs).await?;
debug!(target: "net::protocol_address::start()", "END");
debug!(target: "net::protocol_address::start()", "END => address={}", self.channel.address());
Ok(()) Ok(())
} }
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"ProtocolAddress" PROTO_NAME
} }
} }

View File

@@ -16,8 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::Arc; use async_std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use smol::Executor; use smol::Executor;

View File

@@ -16,23 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use async_std::sync::Mutex; use async_std::sync::{Arc, Mutex};
use std::sync::Arc;
use futures::Future; use futures::Future;
use log::*; use log::{debug, trace};
use smol::Task; use smol::{Executor, Task};
use crate::{system::ExecutorPtr, Result}; use super::super::channel::ChannelPtr;
use crate::Result;
use super::super::ChannelPtr; /// Pointer to protocol jobs manager
/// Pointer to protocol jobs manager.
pub type ProtocolJobsManagerPtr = Arc<ProtocolJobsManager>; pub type ProtocolJobsManagerPtr = Arc<ProtocolJobsManager>;
/// Manages the tasks for the network protocol. Used by other connection
/// protocols to handle asynchronous task execution across the network. Runs all
/// tasks that are handed to it on an executor that has stopping functionality.
pub struct ProtocolJobsManager { pub struct ProtocolJobsManager {
name: &'static str, name: &'static str,
channel: ChannelPtr, channel: ChannelPtr,
@@ -40,29 +34,29 @@ pub struct ProtocolJobsManager {
} }
impl ProtocolJobsManager { impl ProtocolJobsManager {
/// Create a new protocol jobs manager. /// Create a new protocol jobs manager
pub fn new(name: &'static str, channel: ChannelPtr) -> Arc<Self> { pub fn new(name: &'static str, channel: ChannelPtr) -> ProtocolJobsManagerPtr {
Arc::new(Self { name, channel, tasks: Mutex::new(Vec::new()) }) Arc::new(Self { name, channel, tasks: Mutex::new(vec![]) })
} }
/// Runs the task on an executor. Prepares to stop all tasks when the /// Runs the task on an executor
/// channel is closed. pub fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) {
pub fn start(self: Arc<Self>, executor: ExecutorPtr<'_>) {
executor.spawn(self.handle_stop()).detach() executor.spawn(self.handle_stop()).detach()
} }
/// Spawns a new task and adds it to the internal queue. /// Spawns a new task and adds it to the internal queue
pub async fn spawn<'a, F>(&self, future: F, executor: ExecutorPtr<'a>) pub async fn spawn<'a, F>(&self, future: F, executor: Arc<Executor<'a>>)
where where
F: Future<Output = Result<()>> + Send + 'a, F: Future<Output = Result<()>> + Send + 'a,
{ {
self.tasks.lock().await.push(executor.spawn(future)) self.tasks.lock().await.push(executor.spawn(future))
} }
/// Waits for a stop signal, then closes all tasks. Insures that all tasks /// Waits for a stop signal, then closes all tasks.
/// are stopped when a channel closes. Called in start(). /// Ensures that all tasks are stopped when a channel closes.
/// Called in `start()`
async fn handle_stop(self: Arc<Self>) { async fn handle_stop(self: Arc<Self>) {
let stop_sub = self.channel.clone().subscribe_stop().await; let stop_sub = self.channel.subscribe_stop().await;
if stop_sub.is_ok() { if stop_sub.is_ok() {
// Wait for the stop signal // Wait for the stop signal
@@ -72,19 +66,24 @@ impl ProtocolJobsManager {
self.close_all_tasks().await self.close_all_tasks().await
} }
/// Closes all open tasks. Takes all the tasks from the internal queue and /// Closes all open tasks. Takes all the tasks from the internal queue.
/// closes them.
async fn close_all_tasks(self: Arc<Self>) { async fn close_all_tasks(self: Arc<Self>) {
debug!(target: "net::protocol_jobs_manager", debug!(
target: "net::protocol_jobs_manager",
"ProtocolJobsManager::close_all_tasks() [START, name={}, addr={}]", "ProtocolJobsManager::close_all_tasks() [START, name={}, addr={}]",
self.name, self.name, self.channel.address(),
self.channel.address()
); );
// Take all the tasks from our internal queue...
let tasks = std::mem::take(&mut *self.tasks.lock().await); let tasks = std::mem::take(&mut *self.tasks.lock().await);
trace!(target: "net::protocol_jobs_manager", "Cancelling {} tasks", tasks.len());
let mut i = 0;
#[allow(clippy::explicit_counter_loop)]
for task in tasks { for task in tasks {
// ... and cancel them trace!(target: "net::protocol_jobs_manager", "Cancelling task #{}", i);
let _ = task.cancel().await; let _ = task.cancel().await;
trace!(target: "net::protocol_jobs_manager", "Cancelled task #{}", i);
i += 1;
} }
} }
} }

View File

@@ -16,135 +16,152 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{sync::Arc, time::Instant}; use std::time::Instant;
use async_std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use log::{debug, error}; use log::{debug, error};
use rand::Rng; use rand::{rngs::OsRng, Rng};
use smol::Executor; use smol::Executor;
use super::{
super::{
channel::ChannelPtr,
message::{PingMessage, PongMessage},
message_subscriber::MessageSubscription,
p2p::P2pPtr,
settings::SettingsPtr,
},
protocol_base::{ProtocolBase, ProtocolBasePtr},
protocol_jobs_manager::{ProtocolJobsManager, ProtocolJobsManagerPtr},
};
use crate::{util::async_util::sleep, Error, Result}; use crate::{util::async_util::sleep, Error, Result};
use super::{ /// Defines ping and pong messages
super::{message, message_subscriber::MessageSubscription, ChannelPtr, P2pPtr, SettingsPtr},
ProtocolBase, ProtocolBasePtr, ProtocolJobsManager, ProtocolJobsManagerPtr,
};
/// Defines ping and pong messages.
pub struct ProtocolPing { pub struct ProtocolPing {
channel: ChannelPtr, channel: ChannelPtr,
ping_sub: MessageSubscription<message::PingMessage>, ping_sub: MessageSubscription<PingMessage>,
pong_sub: MessageSubscription<message::PongMessage>, pong_sub: MessageSubscription<PongMessage>,
settings: SettingsPtr, settings: SettingsPtr,
jobsman: ProtocolJobsManagerPtr, jobsman: ProtocolJobsManagerPtr,
} }
const PROTO_NAME: &str = "ProtocolPing";
impl ProtocolPing { impl ProtocolPing {
/// Create a new ping-pong protocol. /// Create a new ping-pong protocol.
pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr { pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr {
let settings = p2p.settings(); let settings = p2p.settings();
// Creates a subscription to ping message. // Creates a subscription to ping message
let ping_sub = channel let ping_sub =
.clone() channel.subscribe_msg::<PingMessage>().await.expect("Missing ping dispatcher!");
.subscribe_msg::<message::PingMessage>()
.await
.expect("Missing ping dispatcher!");
// Creates a subscription to pong message. // Creates a subscription to pong message
let pong_sub = channel let pong_sub =
.clone() channel.subscribe_msg::<PongMessage>().await.expect("Missing pong dispatcher!");
.subscribe_msg::<message::PongMessage>()
.await
.expect("Missing pong dispatcher!");
Arc::new(Self { Arc::new(Self {
channel: channel.clone(), channel: channel.clone(),
ping_sub, ping_sub,
pong_sub, pong_sub,
settings, settings,
jobsman: ProtocolJobsManager::new("ProtocolPing", channel), jobsman: ProtocolJobsManager::new(PROTO_NAME, channel),
}) })
} }
/// Runs ping-pong protocol. Creates a subscription to pong, then starts a /// Runs the ping-pong protocol. Creates a subscription to pong, then
/// loop. Loop sleeps for the duration of the channel heartbeat, then /// starts a loop. Loop sleeps for the duration of the channel heartbeat,
/// sends a ping message with a random nonce. Loop starts a timer, waits /// then sends a ping message with a random nonce. Loop starts a timer,
/// for the pong reply and insures the nonce is the same. /// waits for the pong reply and ensures the nonce is the same.
async fn run_ping_pong(self: Arc<Self>) -> Result<()> { async fn run_ping_pong(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_ping::run_ping_pong()", "START"); debug!(
loop { target: "net::protocol_ping::run_ping_pong()",
// Wait channel_heartbeat amount of time. "START => address={}", self.channel.address(),
sleep(self.settings.channel_heartbeat_seconds.into()).await; );
loop {
// Create a random nonce. // Create a random nonce.
let nonce = Self::random_nonce(); let nonce = Self::random_nonce();
// Send ping message. // Send ping message.
let ping = message::PingMessage { nonce }; let ping = PingMessage { nonce };
self.channel.clone().send(ping).await?; self.channel.send(&ping).await?;
debug!(target: "net::protocol_ping::run_ping_pong()", "Send Ping message");
// Start the timer for ping timer. // Start the timer for the ping timer
let start = Instant::now(); let timer = Instant::now();
// Wait for pong, check nonce matches. // Wait for pong, check nonce matches.
let pong_msg = self.pong_sub.receive().await?; let pong_msg = self.pong_sub.receive().await?;
if pong_msg.nonce != nonce { if pong_msg.nonce != nonce {
// TODO: this is too extreme
error!( error!(
target: "net::protocol_ping::run_ping_pong()", target: "net::protocol_ping::run_ping_pong()",
"Wrong nonce for ping reply. Disconnecting from channel." "[P2P] Wrong nonce in pingpong, disconnecting {}",
self.channel.address(),
); );
self.channel.stop().await; self.channel.stop().await;
return Err(Error::ChannelStopped) return Err(Error::ChannelStopped)
} }
let duration = start.elapsed().as_millis();
debug!( debug!(
target: "net::protocol_ping::run_ping_pong()", target: "net::protocol_ping::run_ping_pong()",
"Received Pong message {}ms from [{:?}]", "Received Pong from {}: {}ms",
duration, timer.elapsed().as_millis(),
self.channel.address() self.channel.address(),
);
// Sleep until next heartbeat
sleep(self.settings.channel_heartbeat_interval).await;
}
}
/// Waits for ping, then replies with pong.
/// Copies ping's nonce into the pong reply.
async fn reply_to_ping(self: Arc<Self>) -> Result<()> {
debug!(
target: "net::protocol_ping::reply_to_ping()",
"START => address={}", self.channel.address(),
);
loop {
// Wait for ping, reply with pong that has a matching nonce.
let ping = self.ping_sub.receive().await?;
debug!(
target: "net::protocol_ping::reply_to_ping()",
"Received Ping from {}", self.channel.address(),
);
// Send pong message
let pong = PongMessage { nonce: ping.nonce };
self.channel.send(&pong).await?;
debug!(
target: "net::protocol_ping::reply_to_ping()",
"Sent Pong reply to {}", self.channel.address(),
); );
} }
} }
/// Waits for ping, then replies with pong. Copies ping's nonce into the fn random_nonce() -> u16 {
/// pong reply. OsRng::gen(&mut OsRng)
async fn reply_to_ping(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_ping::reply_to_ping()", "START");
loop {
// Wait for ping, reply with pong that has a matching nonce.
let ping = self.ping_sub.receive().await?;
debug!(target: "net::protocol_ping::reply_to_ping()", "Received Ping message");
// Send pong message.
let pong = message::PongMessage { nonce: ping.nonce };
self.channel.clone().send(pong).await?;
debug!(target: "net::protocol_ping::reply_to_ping()", "Sent Pong reply");
}
}
fn random_nonce() -> u32 {
let mut rng = rand::thread_rng();
rng.gen()
} }
} }
#[async_trait] #[async_trait]
impl ProtocolBase for ProtocolPing { impl ProtocolBase for ProtocolPing {
/// Starts ping-pong keep-alive messages exchange. Runs ping-pong in the /// Starts ping-pong keepalive messages exchange. Runs ping-pong in the
/// protocol task manager, then queues the reply. Sends out a ping and /// protocol task manager, then queues the reply. Sends out a ping and
/// waits for pong reply. Waits for ping and replies with a pong. /// waits for pong reply. Waits for ping and replies with a pong.
async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { async fn start(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
debug!(target: "net::protocol_ping::start()", "START"); debug!(target: "net::protocol_ping::start()", "START => address={}", self.channel.address());
self.jobsman.clone().start(executor.clone()); self.jobsman.clone().start(ex.clone());
self.jobsman.clone().spawn(self.clone().run_ping_pong(), executor.clone()).await; self.jobsman.clone().spawn(self.clone().run_ping_pong(), ex.clone()).await;
self.jobsman.clone().spawn(self.reply_to_ping(), executor).await; self.jobsman.clone().spawn(self.clone().reply_to_ping(), ex).await;
debug!(target: "net::protocol_ping::start()", "END"); debug!(target: "net::protocol_ping::start()", "END => address={}", self.channel.address());
Ok(()) Ok(())
} }
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"ProtocolPing" PROTO_NAME
} }
} }

View File

@@ -17,36 +17,30 @@
*/ */
use async_std::sync::Mutex; use async_std::sync::Mutex;
use std::future::Future; use futures::{future::BoxFuture, Future};
use futures::future::BoxFuture;
use log::debug; use log::debug;
use super::{ use super::{
super::{session::SessionBitflag, ChannelPtr, P2pPtr}, super::{channel::ChannelPtr, p2p::P2pPtr, session::SessionBitFlag},
ProtocolBasePtr, protocol_base::ProtocolBasePtr,
}; };
type Constructor = type Constructor =
Box<dyn Fn(ChannelPtr, P2pPtr) -> BoxFuture<'static, ProtocolBasePtr> + Send + Sync>; Box<dyn Fn(ChannelPtr, P2pPtr) -> BoxFuture<'static, ProtocolBasePtr> + Send + Sync>;
#[derive(Default)]
pub struct ProtocolRegistry { pub struct ProtocolRegistry {
protocol_constructors: Mutex<Vec<(SessionBitflag, Constructor)>>, constructors: Mutex<Vec<(SessionBitFlag, Constructor)>>,
}
impl Default for ProtocolRegistry {
fn default() -> Self {
Self::new()
}
} }
impl ProtocolRegistry { impl ProtocolRegistry {
/// Instantiate a new [`ProtocolRegistry`]
pub fn new() -> Self { pub fn new() -> Self {
Self { protocol_constructors: Mutex::new(Vec::new()) } Self::default()
} }
// add_protocol()? /// `add_protocol()?`
pub async fn register<C, F>(&self, session_flags: SessionBitflag, constructor: C) pub async fn register<C, F>(&self, session_flags: SessionBitFlag, constructor: C)
where where
C: 'static + Fn(ChannelPtr, P2pPtr) -> F + Send + Sync, C: 'static + Fn(ChannelPtr, P2pPtr) -> F + Send + Sync,
F: 'static + Future<Output = ProtocolBasePtr> + Send, F: 'static + Future<Output = ProtocolBasePtr> + Send,
@@ -54,28 +48,30 @@ impl ProtocolRegistry {
let constructor = move |channel, p2p| { let constructor = move |channel, p2p| {
Box::pin(constructor(channel, p2p)) as BoxFuture<'static, ProtocolBasePtr> Box::pin(constructor(channel, p2p)) as BoxFuture<'static, ProtocolBasePtr>
}; };
self.protocol_constructors.lock().await.push((session_flags, Box::new(constructor)));
self.constructors.lock().await.push((session_flags, Box::new(constructor)));
} }
pub async fn attach( pub async fn attach(
&self, &self,
selector_id: SessionBitflag, selector_id: SessionBitFlag,
channel: ChannelPtr, channel: ChannelPtr,
p2p: P2pPtr, p2p: P2pPtr,
) -> Vec<ProtocolBasePtr> { ) -> Vec<ProtocolBasePtr> {
let mut protocols: Vec<ProtocolBasePtr> = Vec::new(); let mut protocols = vec![];
for (session_flags, construct) in self.protocol_constructors.lock().await.iter() {
for (session_flags, construct) in self.constructors.lock().await.iter() {
// Skip protocols that are not registered for this session // Skip protocols that are not registered for this session
if selector_id & session_flags == 0 { if selector_id & session_flags == 0 {
debug!(target: "net::protocol_registry", "Skipping {selector_id:#b}, {session_flags:#b}"); debug!(target: "net::protocol_registry", "Skipping {selector_id:#b}, {session_flags:#b}");
continue continue
} }
let protocol: ProtocolBasePtr = construct(channel.clone(), p2p.clone()).await; let protocol = construct(channel.clone(), p2p.clone()).await;
debug!(target: "net::protocol_registry", "Attached {}", protocol.name()); debug!(target: "net::protocol_registry", "Attached {}", protocol.name());
protocols.push(protocol);
protocols.push(protocol)
} }
protocols protocols
} }
} }

View File

@@ -16,87 +16,98 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::Arc; use async_std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use log::debug; use log::debug;
use smol::Executor; use smol::Executor;
use crate::Result;
use super::{ use super::{
super::{ super::{
message, message_subscriber::MessageSubscription, ChannelPtr, HostsPtr, P2pPtr, SettingsPtr, channel::ChannelPtr,
hosts::HostsPtr,
message::{AddrsMessage, GetAddrsMessage},
message_subscriber::MessageSubscription,
p2p::P2pPtr,
settings::SettingsPtr,
}, },
ProtocolBase, ProtocolBasePtr, protocol_base::{ProtocolBase, ProtocolBasePtr},
}; };
use crate::Result;
/// Implements the seed protocol. /// Implements the seed protocol
pub struct ProtocolSeed { pub struct ProtocolSeed {
channel: ChannelPtr, channel: ChannelPtr,
hosts: HostsPtr, hosts: HostsPtr,
settings: SettingsPtr, settings: SettingsPtr,
addr_sub: MessageSubscription<message::AddrsMessage>, addr_sub: MessageSubscription<AddrsMessage>,
} }
const PROTO_NAME: &str = "ProtocolSeed";
impl ProtocolSeed { impl ProtocolSeed {
/// Create a new seed protocol. /// Create a new seed protocol.
pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr { pub async fn init(channel: ChannelPtr, p2p: P2pPtr) -> ProtocolBasePtr {
let hosts = p2p.hosts(); let hosts = p2p.hosts();
let settings = p2p.settings(); let settings = p2p.settings();
//// Create a subscription to address message. // Create a subscription to address message
let addr_sub = channel let addr_sub =
.clone() channel.subscribe_msg::<AddrsMessage>().await.expect("Missing addr dispatcher!");
.subscribe_msg::<message::AddrsMessage>()
.await
.expect("Missing addr dispatcher!");
Arc::new(Self { channel, hosts, settings, addr_sub }) Arc::new(Self { channel, hosts, settings, addr_sub })
} }
/// Sends own external addresses over a channel. Imports own external addresses /// Sends own external addresses over a channel. Imports own external addresses
/// from settings, then adds that addresses to an address message and /// from settings, then adds those addresses to an addrs message and sends it
/// sends it out over the channel. /// out over the channel.
pub async fn send_self_address(&self) -> Result<()> { pub async fn send_self_address(&self) -> Result<()> {
debug!(target: "net::protocol_seed::send_self_address()", "[START]");
// Do nothing if external addresses are not configured // Do nothing if external addresses are not configured
if self.settings.external_addr.is_empty() { if self.settings.external_addrs.is_empty() {
return Ok(()) return Ok(())
} }
let ext_addrs = self.settings.external_addr.clone(); let addrs = self.settings.external_addrs.clone();
debug!(target: "net::protocol_seed::send_self_address()", "ext_addrs={:?}", ext_addrs); debug!(
let ext_addr_msg = message::ExtAddrsMessage { ext_addrs }; target: "net::protocol_seed::send_self_address()",
self.channel.clone().send(ext_addr_msg).await "ext_addrs={:?}, dest={}", addrs, self.channel.address(),
);
let ext_addr_msg = AddrsMessage { addrs };
self.channel.send(&ext_addr_msg).await?;
debug!(target: "net::protocol_seed::send_self_address()", "[END]");
Ok(())
} }
} }
#[async_trait] #[async_trait]
impl ProtocolBase for ProtocolSeed { impl ProtocolBase for ProtocolSeed {
/// Starts the seed protocol. Creates a subscription to the address message, /// Starts the seed protocol. Creates a subscription to the address message,
/// then sends our address to the seed server. Sends a get-address /// then sends our address to the seed server. Sends a get-address message
/// message and receives an address message. /// and receives an address messsage.
async fn start(self: Arc<Self>, _executor: Arc<Executor<'_>>) -> Result<()> { async fn start(self: Arc<Self>, _ex: Arc<Executor<'_>>) -> Result<()> {
debug!(target: "net::protocol_seed::start()", "START"); debug!(target: "net::protocol_seed::start()", "START => address={}", self.channel.address());
// Send own address to the seed server. // Send own address to the seed server
self.send_self_address().await?; self.send_self_address().await?;
// Send get address message. // Send get address message
let get_addr = message::GetAddrsMessage {}; let get_addr = GetAddrsMessage { max: self.settings.outbound_connections as u32 };
self.channel.clone().send(get_addr).await?; self.channel.send(&get_addr).await?;
// Receive addresses. // Receive addresses
let addrs_msg = self.addr_sub.receive().await?; let addrs_msg = self.addr_sub.receive().await?;
debug!(target: "net::protocol_seed::start()", "Received {} addrs", addrs_msg.addrs.len() debug!(
target: "net::protocol_seed::start()",
"Received {} addrs from {}", addrs_msg.addrs.len(), self.channel.address(),
); );
self.hosts.store(addrs_msg.addrs.clone()).await; self.hosts.store(&addrs_msg.addrs).await;
debug!(target: "net::protocol_seed::start()", "END"); debug!(target: "net::protocol_seed::start()", "END => address={}", self.channel.address());
Ok(()) Ok(())
} }
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"ProtocolSeed" PROTO_NAME
} }
} }

View File

@@ -16,164 +16,165 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use async_std::future::timeout; use std::time::Duration;
use std::{sync::Arc, time::Duration};
use log::*; use async_std::{future::timeout, sync::Arc};
use log::{debug, error};
use smol::Executor; use smol::Executor;
use super::super::{
channel::ChannelPtr,
hosts::HostsPtr,
message::{VerackMessage, VersionMessage},
message_subscriber::MessageSubscription,
settings::SettingsPtr,
};
use crate::{Error, Result}; use crate::{Error, Result};
use super::super::{ /// Implements the protocol version handshake sent out by nodes at
message, message_subscriber::MessageSubscription, ChannelPtr, HostsPtr, SettingsPtr, /// the beginning of a connection.
};
/// Implements the protocol version handshake sent out by nodes at the beginning
/// of a connection.
pub struct ProtocolVersion { pub struct ProtocolVersion {
channel: ChannelPtr, channel: ChannelPtr,
version_sub: MessageSubscription<message::VersionMessage>, version_sub: MessageSubscription<VersionMessage>,
verack_sub: MessageSubscription<message::VerackMessage>, verack_sub: MessageSubscription<VerackMessage>,
settings: SettingsPtr, settings: SettingsPtr,
hosts: HostsPtr, hosts: HostsPtr,
} }
impl ProtocolVersion { impl ProtocolVersion {
/// Create a new version protocol. Makes a version and version /// Create a new version protocol. Makes a version and version ack
/// acknowledgement subscription, then adds them to a version protocol /// subscription, then adds them to a version protocol instance.
/// instance.
pub async fn new(channel: ChannelPtr, settings: SettingsPtr, hosts: HostsPtr) -> Arc<Self> { pub async fn new(channel: ChannelPtr, settings: SettingsPtr, hosts: HostsPtr) -> Arc<Self> {
// Creates a version subscription. // Creates a versi5on subscription
let version_sub = channel let version_sub =
.clone() channel.subscribe_msg::<VersionMessage>().await.expect("Missing version dispatcher!");
.subscribe_msg::<message::VersionMessage>()
.await
.expect("Missing version dispatcher!");
// Creates a version acknowledgement subscription. // Creates a version acknowledgement subscription
let verack_sub = channel let verack_sub =
.clone() channel.subscribe_msg::<VerackMessage>().await.expect("Missing verack dispatcher!");
.subscribe_msg::<message::VerackMessage>()
.await
.expect("Missing verack dispatcher!");
Arc::new(Self { channel, version_sub, verack_sub, settings, hosts }) Arc::new(Self { channel, version_sub, verack_sub, settings, hosts })
} }
/// Start version information exchange. Start the timer. Send version info /// Start version information exchange. Start the timer. Send version
/// and wait for version acknowledgement. Wait for version info and send /// info and wait for version ack. Wait for version info and send
/// version acknowledgement. /// version ack.
pub async fn run(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { pub async fn run(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> {
debug!(target: "net::protocol_version::run()", "START"); debug!(target: "net::protocol_version::run()", "START => address={}", self.channel.address());
// Start timer // Start timer
// Send version, wait for verack // Send version, wait for verack
// Wait for version, send verack // Wait for version, send verack
// Fin. // Fin.
let result = timeout( let result = timeout(
Duration::from_secs(self.settings.channel_handshake_seconds.into()), Duration::from_secs(self.settings.channel_handshake_timeout),
self.clone().exchange_versions(executor), self.clone().exchange_versions(executor),
) )
.await; .await;
if let Err(_e) = result { if let Err(e) = result {
error!(
target: "net::protocol_version::run()",
"[P2P] Version Exchange failed [{}]: {}",
self.channel.address(), e,
);
// Remove from hosts
self.hosts.remove(self.channel.address()).await;
self.channel.stop().await;
return Err(Error::ChannelTimeout) return Err(Error::ChannelTimeout)
} }
debug!(target: "net::protocol_version::run()", "END"); debug!(target: "net::protocol_version::run()", "END => address={}", self.channel.address());
Ok(()) Ok(())
} }
/// Send and recieve version information. /// Send and receive version information
async fn exchange_versions(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { async fn exchange_versions(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> {
debug!(target: "net::protocol_version::exchange_versions()", "START"); debug!(
target: "net::protocol_version::exchange_versions()",
"START => address={}", self.channel.address(),
);
let send = executor.spawn(self.clone().send_version()); let send = executor.spawn(self.clone().send_version());
let recv = executor.spawn(self.recv_version()); let recv = executor.spawn(self.clone().recv_version());
send.await?; send.await?;
recv.await?; recv.await?;
debug!(target: "net::protocol_version::exchange_versions()", "END"); debug!(
target: "net::protocol_version::exchange_versions()",
"END => address={}", self.channel.address(),
);
Ok(()) Ok(())
} }
/// Send version info and wait for version acknowledgement /// Send version info and wait for version acknowledgement.
/// and ensures the app version is the same, if configured. /// Ensures that the app version is the same.
async fn send_version(self: Arc<Self>) -> Result<()> { async fn send_version(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_version::send_version()", "START"); debug!(
target: "net::protocol_version::send_version()",
"START => address={}", self.channel.address(),
);
let version = message::VersionMessage { node_id: self.settings.node_id.clone() }; let version = VersionMessage { node_id: self.settings.node_id.clone() };
self.channel.send(&version).await?;
self.channel.clone().send(version).await?; // Wait for verack
// Wait for version acknowledgement
let verack_msg = self.verack_sub.receive().await?; let verack_msg = self.verack_sub.receive().await?;
// Validate peer received version against our version, if configured. // Validate peer received version against our version.
// Seeds version gets ignored. // Seeds get ignored
if !self.settings.seeds.contains(&self.channel.address()) { if self.settings.seeds.contains(self.channel.address()) {
match &self.settings.app_version { debug!(target: "net::protocol_version::send_version()", "Peer is a seed, skipping version");
Some(app_version) => { debug!(target: "net::protocol_version::send_version()", "END => address={}", self.channel.address());
debug!( return Ok(())
target: "net::protocol_version::send_version()",
"App version: {}, received version: {}",
app_version,
verack_msg.app
);
// Version format: MAJOR.MINOR.PATCH
let app_versions: Vec<&str> = app_version.split('.').collect();
let verack_msg_versions: Vec<&str> = verack_msg.app.split('.').collect();
// Check for malformed versions
if app_versions.len() != 3 || verack_msg_versions.len() != 3 {
error!(
target: "net::protocol_version::send_version()",
"Malformed version detected. Disconnecting from channel."
);
self.hosts.remove(&self.channel.address()).await;
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
// Ignore PATCH version
if app_versions[0] != verack_msg_versions[0] ||
app_versions[1] != verack_msg_versions[1]
{
error!(
target: "net::protocol_version::send_version()",
"Wrong app version from ({}). Disconnecting from channel.",
self.channel.address()
);
self.hosts.remove(&self.channel.address()).await;
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
}
None => {
debug!(
target: "net::protocol_version::send_version()",
"App version not set, ignoring received"
)
}
}
} }
debug!(target: "net::protocol_version::send_version()", "END"); debug!(
target: "net::protocol_version::send_version()",
"App version: {}, Recv version: {}",
self.settings.app_version, verack_msg.app_version,
);
// MAJOR and MINOR should be the same.
if self.settings.app_version.major != verack_msg.app_version.major ||
self.settings.app_version.minor != verack_msg.app_version.minor
{
error!(
target: "net::protocol_version::send_version()",
"[P2P] Version mismatch from {}. Disconnecting...",
self.channel.address(),
);
self.hosts.remove(self.channel.address()).await;
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
// Versions are compatible
Ok(()) Ok(())
} }
/// Recieve version info, check the message is okay and send version /// Receive version info, check the message is okay and send verack
/// acknowledgement with app version attached. /// with app version attached.
async fn recv_version(self: Arc<Self>) -> Result<()> { async fn recv_version(self: Arc<Self>) -> Result<()> {
debug!(target: "net::protocol_version::recv_version()", "START"); debug!(
target: "net::protocol_version::recv_version()",
"START => address={}", self.channel.address(),
);
// Receive version message // Receive version message
let version = self.version_sub.receive().await?; let _version = self.version_sub.receive().await?;
self.channel.set_remote_node_id(version.node_id.clone()).await; //self.channel.set_remote_node_id(version.node_id.clone()).await;
// Send version acknowledgement // Send verack
let verack = let verack = VerackMessage { app_version: self.settings.app_version.clone() };
message::VerackMessage { app: self.settings.app_version.clone().unwrap_or_default() }; self.channel.send(&verack).await?;
self.channel.clone().send(verack).await?;
debug!(target: "net::protocol_version::recv_version()", "END"); debug!(
target: "net::protocol_version::recv_version()",
"END => address={}", self.channel.address(),
);
Ok(()) Ok(())
} }
} }

View File

@@ -15,6 +15,14 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
//! Inbound connections session. Manages the creation of inbound sessions.
//! Used to create an inbound session and start and stop the session.
//!
//! Class consists of 3 pointers: a weak pointer to the p2p parent class,
//! an acceptor pointer, and a stoppable task pointer. Using a weak pointer
//! to P2P allows us to avoid circular dependencies.
use std::collections::HashMap; use std::collections::HashMap;
use async_std::sync::{Arc, Mutex, Weak}; use async_std::sync::{Arc, Mutex, Weak};
@@ -24,16 +32,22 @@ use serde_json::json;
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use super::{
super::{
acceptor::{Acceptor, AcceptorPtr},
channel::ChannelPtr,
p2p::{P2p, P2pPtr},
},
Session, SessionBitFlag, SESSION_INBOUND,
};
use crate::{ use crate::{
system::{StoppableTask, StoppableTaskPtr}, system::{StoppableTask, StoppableTaskPtr},
Error, Result, Error, Result,
}; };
use super::{ pub type InboundSessionPtr = Arc<InboundSession>;
super::{Acceptor, AcceptorPtr, ChannelPtr, P2p},
Session, SessionBitflag, SESSION_INBOUND,
};
/// Channel debug info
struct InboundInfo { struct InboundInfo {
channel: ChannelPtr, channel: ChannelPtr,
} }
@@ -44,7 +58,7 @@ impl InboundInfo {
} }
} }
/// Defines inbound connections session. /// Defines inbound connections session
pub struct InboundSession { pub struct InboundSession {
p2p: Weak<P2p>, p2p: Weak<P2p>,
acceptors: Mutex<Vec<AcceptorPtr>>, acceptors: Mutex<Vec<AcceptorPtr>>,
@@ -53,39 +67,39 @@ pub struct InboundSession {
} }
impl InboundSession { impl InboundSession {
/// Create a new inbound session. /// Create a new inbound session
pub async fn new(p2p: Weak<P2p>) -> Arc<Self> { pub fn new(p2p: Weak<P2p>) -> InboundSessionPtr {
Arc::new(Self { Arc::new(Self {
p2p, p2p,
acceptors: Mutex::new(Vec::new()), acceptors: Mutex::new(vec![]),
accept_tasks: Mutex::new(Vec::new()), accept_tasks: Mutex::new(vec![]),
connect_infos: Mutex::new(Vec::new()), connect_infos: Mutex::new(vec![]),
}) })
} }
/// Starts the inbound session. Begins by accepting connections and fails if /// Starts the inbound session. Begins by accepting connections and fails
/// the addresses are not configured. Then runs the channel subscription /// if the addresses are not configured. Then runs the channel subscription
/// loop. /// loop.
pub async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { pub async fn start(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
if self.p2p().settings().inbound.is_empty() { if self.p2p().settings().inbound_addrs.is_empty() {
info!(target: "net::inbound_session", "Not configured for accepting incoming connections."); info!(target: "net::inbound_session", "[P2P] Not configured for inbound connections.");
return Ok(()) return Ok(())
} }
// Activate mutex lock on accept tasks. // Activate mutex lock on accept tasks.
let mut accept_tasks = self.accept_tasks.lock().await; let mut accept_tasks = self.accept_tasks.lock().await;
for (index, accept_addr) in self.p2p().settings().inbound.iter().enumerate() { for (index, accept_addr) in self.p2p().settings().inbound_addrs.iter().enumerate() {
self.clone().start_accept_session(index, accept_addr.clone(), executor.clone()).await?; self.clone().start_accept_session(index, accept_addr.clone(), ex.clone()).await?;
let task = StoppableTask::new(); let task = StoppableTask::new();
task.clone().start( task.clone().start(
self.clone().channel_sub_loop(index, executor.clone()), self.clone().channel_sub_loop(index, ex.clone()),
// Ignore stop handler // Ignore stop handler
|_| async {}, |_| async {},
Error::NetworkServiceStopped, Error::NetworkServiceStopped,
executor.clone(), ex.clone(),
); );
self.connect_infos.lock().await.push(HashMap::new()); self.connect_infos.lock().await.push(HashMap::new());
@@ -113,79 +127,75 @@ impl InboundSession {
self: Arc<Self>, self: Arc<Self>,
index: usize, index: usize,
accept_addr: Url, accept_addr: Url,
executor: Arc<Executor<'_>>, ex: Arc<Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
info!(target: "net::inbound_session", "#{} starting inbound session on {}", index, accept_addr); info!(target: "net::inbound_session", "[P2P] Starting Inbound session #{} on {}", index, accept_addr);
// Generate a new acceptor for this inbound session // Generate a new acceptor for this inbound session
let acceptor = Acceptor::new(Mutex::new(None)); let acceptor = Acceptor::new(Mutex::new(None));
let parent = Arc::downgrade(&self); let parent = Arc::downgrade(&self);
*acceptor.session.lock().await = Some(Arc::new(parent)); *acceptor.session.lock().await = Some(Arc::new(parent));
// Start listener // Start listener
let result = acceptor.clone().start(accept_addr, executor).await; let result = acceptor.clone().start(accept_addr, ex).await;
if let Err(err) = result.clone() { if let Err(e) = result.clone() {
error!(target: "net::inbound_session", "#{} error starting listener: {}", index, err); error!(target: "net::inbound_session", "[P2P] Error starting listener #{}: {}", index, e);
acceptor.stop().await;
} else {
self.acceptors.lock().await.push(acceptor);
} }
self.acceptors.lock().await.push(acceptor);
result result
} }
/// Wait for all new channels created by the acceptor and call /// Wait for all new channels created by the acceptor and call setup_channel() on them.
/// setup_channel() on them. async fn channel_sub_loop(self: Arc<Self>, index: usize, ex: Arc<Executor<'_>>) -> Result<()> {
async fn channel_sub_loop(
self: Arc<Self>,
index: usize,
executor: Arc<Executor<'_>>,
) -> Result<()> {
let channel_sub = self.acceptors.lock().await[index].clone().subscribe().await; let channel_sub = self.acceptors.lock().await[index].clone().subscribe().await;
loop { loop {
let channel = channel_sub.receive().await?; let channel = channel_sub.receive().await?;
// Spawn a detached task to process the channel // Spawn a detached task to process the channel.
// This will just perform the channel setup then exit. // This will just perform the channel setup then exit.
executor.spawn(self.clone().setup_channel(index, channel, executor.clone())).detach(); ex.spawn(self.clone().setup_channel(index, channel, ex.clone())).detach();
} }
} }
/// Registers the channel. First performs a network handshake and starts the /// Registers the channel. First performs a network handshake and starts the channel.
/// channel. Then starts sending keep-alive and address messages across the /// Then starts sending keep-alive and address messages across the channel.
/// channel.
async fn setup_channel( async fn setup_channel(
self: Arc<Self>, self: Arc<Self>,
index: usize, index: usize,
channel: ChannelPtr, channel: ChannelPtr,
executor: Arc<Executor<'_>>, ex: Arc<Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
info!(target: "net::inbound_session", "#{} connected inbound [{}]", index, channel.address()); info!(target: "net::inbound_session", "[P2P] Connected Inbound #{} [{}]", index, channel.address());
self.register_channel(channel.clone(), ex.clone()).await?;
self.clone().register_channel(channel.clone(), executor.clone()).await?; let addr = channel.address().clone();
self.connect_infos.lock().await[index]
.insert(addr.clone(), InboundInfo { channel: channel.clone() });
self.manage_channel_for_get_info(index, channel).await; let stop_sub = channel.subscribe_stop().await?;
stop_sub.receive().await;
self.connect_infos.lock().await[index].remove(&addr);
Ok(()) Ok(())
} }
async fn manage_channel_for_get_info(&self, index: usize, channel: ChannelPtr) {
let key = channel.address();
self.connect_infos.lock().await[index]
.insert(key.clone(), InboundInfo { channel: channel.clone() });
let stop_sub = channel.subscribe_stop().await;
if stop_sub.is_ok() {
stop_sub.unwrap().receive().await;
}
self.connect_infos.lock().await[index].remove(&key);
}
} }
#[async_trait] #[async_trait]
impl Session for InboundSession { impl Session for InboundSession {
fn p2p(&self) -> P2pPtr {
self.p2p.upgrade().unwrap()
}
fn type_id(&self) -> SessionBitFlag {
SESSION_INBOUND
}
async fn get_info(&self) -> serde_json::Value { async fn get_info(&self) -> serde_json::Value {
let mut infos = HashMap::new(); let mut infos = HashMap::new();
for (index, accept_addr) in self.p2p().settings().inbound.iter().enumerate() { for (index, accept_addr) in self.p2p().settings().inbound_addrs.iter().enumerate() {
let connect_infos = &self.connect_infos.lock().await[index]; let connect_infos = &self.connect_infos.lock().await[index];
for (addr, info) in connect_infos { for (addr, info) in connect_infos {
let json_addr = json!({ "accept_addr": accept_addr }); let json_addr = json!({ "accept_addr": accept_addr });
@@ -193,16 +203,7 @@ impl Session for InboundSession {
infos.insert(addr.to_string(), info); infos.insert(addr.to_string(), info);
} }
} }
json!({
"connected": infos,
})
}
fn p2p(&self) -> Arc<P2p> { json!({ "connected": infos })
self.p2p.upgrade().unwrap()
}
fn type_id(&self) -> SessionBitflag {
SESSION_INBOUND
} }
} }

View File

@@ -16,26 +16,42 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use async_std::sync::{Arc, Mutex, Weak}; //! Manual connections session. Manages the creation of manual sessions.
//! Used to create a manual session and to stop and start the session.
//!
//! A manual session is a type of outbound session in which we attempt
//! connection to a predefined set of peers.
//!
//! Class consists of a weak pointer to the p2p interface and a vector of
//! outbound connection slots. Using a weak pointer to p2p allows us to
//! avoid circular dependencies. The vector of slots is wrapped in a mutex
//! lock. This is switched on every time we instantiate a connection slot
//! and insures that no other part of the program uses the slots at the
//! same time.
use async_std::sync::{Arc, Mutex, Weak};
use async_trait::async_trait; use async_trait::async_trait;
use log::{info, warn}; use log::{info, warn};
use serde_json::json;
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use super::{
super::{
channel::ChannelPtr,
connector::Connector,
p2p::{P2p, P2pPtr},
},
Session, SessionBitFlag, SESSION_MANUAL,
};
use crate::{ use crate::{
net::transport::TransportName, system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr},
system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr, Subscription},
util::async_util::sleep, util::async_util::sleep,
Error, Result, Error, Result,
}; };
use super::{ pub type ManualSessionPtr = Arc<ManualSession>;
super::{ChannelPtr, Connector, P2p},
Session, SessionBitflag, SESSION_MANUAL,
};
/// Defines manual connections session.
pub struct ManualSession { pub struct ManualSession {
p2p: Weak<P2p>, p2p: Weak<P2p>,
connect_slots: Mutex<Vec<StoppableTaskPtr>>, connect_slots: Mutex<Vec<StoppableTaskPtr>>,
@@ -46,17 +62,17 @@ pub struct ManualSession {
} }
impl ManualSession { impl ManualSession {
/// Create a new inbound session. /// Create a new manual session.
pub fn new(p2p: Weak<P2p>) -> Arc<Self> { pub fn new(p2p: Weak<P2p>) -> ManualSessionPtr {
Arc::new(Self { Arc::new(Self {
p2p, p2p,
connect_slots: Mutex::new(Vec::new()), connect_slots: Mutex::new(vec![]),
channel_subscriber: Subscriber::new(), channel_subscriber: Subscriber::new(),
notify: Mutex::new(false), notify: Mutex::new(false),
}) })
} }
/// Stop the outbound session. /// Stops the manual session.
pub async fn stop(&self) { pub async fn stop(&self) {
let connect_slots = &*self.connect_slots.lock().await; let connect_slots = &*self.connect_slots.lock().await;
@@ -65,115 +81,115 @@ impl ManualSession {
} }
} }
pub async fn connect(self: Arc<Self>, addr: &Url, executor: Arc<Executor<'_>>) { /// Connect the manual session to the given address
pub async fn connect(self: Arc<Self>, addr: Url, ex: Arc<Executor<'_>>) {
let task = StoppableTask::new(); let task = StoppableTask::new();
task.clone().start( task.clone().start(
self.clone().channel_connect_loop(addr.clone(), executor.clone()), self.clone().channel_connect_loop(addr, ex.clone()),
// Ignore stop handler // Ignore stop handler
|_| async {}, |_| async {},
Error::NetworkServiceStopped, Error::NetworkServiceStopped,
executor.clone(), ex,
); );
self.connect_slots.lock().await.push(task); self.connect_slots.lock().await.push(task);
} }
/// Creates a connector object and tries to connect using it
pub async fn channel_connect_loop( pub async fn channel_connect_loop(
self: Arc<Self>, self: Arc<Self>,
addr: Url, addr: Url,
executor: Arc<Executor<'_>>, ex: Arc<Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
let parent = Arc::downgrade(&self); let parent = Arc::downgrade(&self);
let settings = self.p2p().settings(); let settings = self.p2p().settings();
let connector = Connector::new(settings.clone(), Arc::new(parent)); let connector = Connector::new(settings.clone(), Arc::new(parent));
let attempts = settings.manual_attempt_limit; let attempts = settings.manual_attempt_limit;
let mut remaining = attempts; let mut remaining = attempts;
// Retrieve preferent outbound transports // Add the peer to list of pending channels
let outbound_transports = &settings.outbound_transports; self.p2p().add_pending(&addr).await;
// Check that addr transport is in configured outbound transport
let addr_transport = TransportName::try_from(addr.clone())?;
let transports = if outbound_transports.contains(&addr_transport) {
vec![addr_transport]
} else {
warn!(target: "net::manual_session", "Manual outbound address {} transport is not in accepted outbound transports, will try with: {:?}", addr, outbound_transports);
outbound_transports.clone()
};
// Loop forever if attempts==0, otherwise loop attempts number of times.
let mut tried_attempts = 0;
loop { loop {
// Loop forever if attempts is 0 tried_attempts += 1;
// Otherwise loop attempts number of times info!(
target: "net::manual_session",
"[P2P] Connecting to manual outbound [{}] (attempt #{})",
addr, tried_attempts,
);
match connector.connect(addr.clone()).await {
Ok(channel) => {
info!(
target: "net::manual_session",
"[P2P] Manual outbound connected [{}]", addr,
);
let stop_sub =
channel.subscribe_stop().await.expect("Channel should not be stopped");
// Register the new channel
self.register_channel(channel.clone(), ex.clone()).await?;
// Channel is now connected but not yet setup
// Remove pending lock since register_channel will add the channel to p2p
self.p2p().remove_pending(&addr).await;
// Notify that channel processing has finished
if *self.notify.lock().await {
self.channel_subscriber.notify(Ok(channel)).await;
}
// Wait for channel to close
stop_sub.receive().await;
info!(
target: "net::manual_session",
"[P2P] Manual outbound disconnected [{}]", addr,
);
// DEV NOTE: Here we can choose to attempt reconnection again
return Ok(())
}
Err(e) => {
warn!(
target: "net::manual_session",
"[P2P] Unable to connect to manual outbound [{}]: {}",
addr, e,
);
}
}
// Wait and try again.
// TODO: Should we notify about the failure now, or after all attempts
// have failed?
if *self.notify.lock().await {
self.channel_subscriber.notify(Err(Error::ConnectFailed)).await;
}
remaining = if attempts == 0 { 1 } else { remaining - 1 }; remaining = if attempts == 0 { 1 } else { remaining - 1 };
if remaining == 0 { if remaining == 0 {
break break
} }
self.p2p().add_pending(addr.clone()).await; info!(
target: "net::manual_session",
for transport in &transports { "[P2P] Waiting {} seconds until next manual outbound connection attempt [{}]",
// Replace addr transport settings.outbound_connect_timeout, addr,
let mut transport_addr = addr.clone(); );
transport_addr.set_scheme(&transport.to_scheme())?; sleep(settings.outbound_connect_timeout).await;
info!(target: "net::manual_session", "Connecting to manual outbound [{}]", transport_addr);
match connector.connect(transport_addr.clone()).await {
Ok(channel) => {
// Blacklist goes here
info!(target: "net::manual_session", "Connected to manual outbound [{}]", transport_addr);
let stop_sub = channel.subscribe_stop().await;
if stop_sub.is_err() {
continue
}
self.clone().register_channel(channel.clone(), executor.clone()).await?;
// Channel is now connected but not yet setup
// Remove pending lock since register_channel will add the channel to p2p
self.p2p().remove_pending(&addr).await;
//self.clone().attach_protocols(channel, executor.clone()).await?;
// Notify that channel processing has been finished
if *self.notify.lock().await {
self.channel_subscriber.notify(Ok(channel)).await;
}
// Wait for channel to close
stop_sub.unwrap().receive().await;
}
Err(err) => {
info!(target: "net::manual_session", "Unable to connect to manual outbound [{}]: {}", addr, err);
}
}
}
// Notify that channel processing has been finished (failed)
if *self.notify.lock().await {
self.channel_subscriber.notify(Err(Error::ConnectFailed)).await;
}
sleep(settings.connect_timeout_seconds.into()).await;
} }
warn!( warn!(
target: "net::manual_session", target: "net::manual_session",
"Suspending manual connection to [{}] after {} failed attempts.", "[P2P] Suspending manual connection to {} after {} failed attempts",
&addr, addr, attempts,
attempts
); );
Ok(()) self.p2p().remove_pending(&addr).await;
}
/// Subscribe to a channel. Ok(())
pub async fn subscribe_channel(&self) -> Subscription<Result<ChannelPtr>> {
self.channel_subscriber.clone().subscribe().await
} }
/// Enable channel_subscriber notifications. /// Enable channel_subscriber notifications.
@@ -185,38 +201,19 @@ impl ManualSession {
pub async fn disable_notify(self: Arc<Self>) { pub async fn disable_notify(self: Arc<Self>) {
*self.notify.lock().await = false; *self.notify.lock().await = false;
} }
// Starts sending keep-alive and address messages across the channels.
/*async fn attach_protocols(
self: Arc<Self>,
channel: ChannelPtr,
executor: Arc<Executor<'_>>,
) -> Result<()> {
let hosts = self.p2p().hosts();
let protocol_ping = ProtocolPing::new(channel.clone(), self.p2p());
let protocol_addr = ProtocolAddress::new(channel, hosts).await;
protocol_ping.start(executor.clone()).await;
protocol_addr.start(executor).await;
Ok(())
}*/
} }
#[async_trait] #[async_trait]
impl Session for ManualSession { impl Session for ManualSession {
async fn get_info(&self) -> serde_json::Value { fn p2p(&self) -> P2pPtr {
json!({
"key": 110
})
}
fn p2p(&self) -> Arc<P2p> {
self.p2p.upgrade().unwrap() self.p2p.upgrade().unwrap()
} }
fn type_id(&self) -> SessionBitflag { fn type_id(&self) -> SessionBitFlag {
SESSION_MANUAL SESSION_MANUAL
} }
async fn get_info(&self) -> serde_json::Value {
todo!()
}
} }

View File

@@ -16,77 +16,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::{Arc, Weak}; use async_std::sync::{Arc, Weak};
use async_trait::async_trait; use async_trait::async_trait;
use log::debug; use log::debug;
use smol::Executor; use smol::Executor;
use super::{channel::ChannelPtr, p2p::P2pPtr, protocol::ProtocolVersion};
use crate::Result; use crate::Result;
use super::{p2p::P2pPtr, protocol::ProtocolVersion, ChannelPtr};
/// Seed sync session creates a connection to the seed nodes specified in settings.
/// A new seed sync session is created every time we call p2p::start(). The seed
/// sync session loops through all the configured seeds and tries to connect to
/// them using a Connector. Seed sync either connects successfully,
/// fails with an error or times out.
///
/// If a seed node connects successfully, it runs a version exchange protocol,
/// stores the channel in the p2p list of channels, and disconnects, removing
/// the channel from the channel list.
///
/// The channel is registered using Session trait method, register_channel().
/// This invokes the Protocol Registry method attach(). Usually this returns a
/// list of protocols that we loop through and start. In this case, attach()
/// uses the bitflag selector to identify seed sessions and exclude them.
///
/// The version exchange occurs inside register_channel(). We create a handshake
/// task that runs the version exchange with the function
/// perform_handshake_protocols(). This runs the version exchange protocol,
/// stores the channel in the p2p list of channels, and subscribes to a stop
/// signal.
pub mod seedsync_session;
pub mod manual_session;
/// Inbound connections session. Manages the creation of inbound sessions. Used
/// to create an inbound session and start and stop the session.
///
/// Class consists of 3 pointers: a weak pointer to the p2p parent class, an
/// acceptor pointer, and a stoppable task pointer. Using a weak pointer to P2P
/// allows us to avoid circular dependencies.
pub mod inbound_session; pub mod inbound_session;
pub use inbound_session::{InboundSession, InboundSessionPtr};
/// Outbound connections session. Manages the creation of outbound sessions. pub mod manual_session;
/// Used to create an outbound session and stop and start the session. pub use manual_session::{ManualSession, ManualSessionPtr};
///
/// Class consists of a weak pointer to the p2p interface and a vector
/// of outbound connection slots. Using a weak pointer to p2p allows us to avoid
/// circular dependencies. The vector of slots is wrapped in a mutex lock. This
/// is switched on everytime we instantiate a connection slot and insures that
/// no other part of the program uses the slots at the same time.
pub mod outbound_session; pub mod outbound_session;
pub use outbound_session::{OutboundSession, OutboundSessionPtr};
pub mod seedsync_session;
pub use seedsync_session::{SeedSyncSession, SeedSyncSessionPtr};
// bitwise selectors for the protocol_registry /// Bitwise selectors for the `protocol_registry`
pub type SessionBitflag = u32; pub type SessionBitFlag = u32;
pub const SESSION_INBOUND: SessionBitflag = 0b0001; pub const SESSION_INBOUND: SessionBitFlag = 0b0001;
pub const SESSION_OUTBOUND: SessionBitflag = 0b0010; pub const SESSION_OUTBOUND: SessionBitFlag = 0b0010;
pub const SESSION_MANUAL: SessionBitflag = 0b0100; pub const SESSION_MANUAL: SessionBitFlag = 0b0100;
pub const SESSION_SEED: SessionBitflag = 0b1000; pub const SESSION_SEED: SessionBitFlag = 0b1000;
pub const SESSION_ALL: SessionBitflag = 0b1111; pub const SESSION_ALL: SessionBitFlag = 0b1111;
pub use inbound_session::InboundSession;
pub use manual_session::ManualSession;
pub use outbound_session::OutboundSession;
pub use seedsync_session::SeedSyncSession;
pub type SessionWeakPtr = Arc<Weak<dyn Session + Send + Sync + 'static>>; pub type SessionWeakPtr = Arc<Weak<dyn Session + Send + Sync + 'static>>;
/// Removes channel from the list of connected channels when a stop signal is /// Removes channel from the list of connected channels when a stop signal
/// received. /// is received.
async fn remove_sub_on_stop(p2p: P2pPtr, channel: ChannelPtr) { pub async fn remove_sub_on_stop(p2p: P2pPtr, channel: ChannelPtr) {
debug!(target: "net", "remove_sub_on_stop() [START]"); debug!(target: "net::session::remove_sub_on_stop()", "[START]");
// Subscribe to stop events // Subscribe to stop events
let stop_sub = channel.clone().subscribe_stop().await; let stop_sub = channel.clone().subscribe_stop().await;
@@ -95,37 +55,40 @@ async fn remove_sub_on_stop(p2p: P2pPtr, channel: ChannelPtr) {
stop_sub.unwrap().receive().await; stop_sub.unwrap().receive().await;
} }
debug!(target: "net", debug!(
"remove_sub_on_stop(): received stop event. Removing channel {}", target: "net::session::remove_sub_on_stop()",
channel.address() "Received stop event. Removing channel {}", channel.address(),
); );
// Remove channel from p2p // Remove channel from p2p
p2p.remove(channel).await; p2p.remove(channel).await;
debug!(target: "net", "remove_sub_on_stop() [END]"); debug!(target: "net::session::remove_sub_on_stop()", "[END]");
} }
/// Session trait. Defines methods that are used across sessions.
/// Implements registering the channel and initializing the channel by
/// performing a network handshake.
#[async_trait] #[async_trait]
/// Session trait.
/// Defines methods that are used across sessions. Implements registering the
/// channel and initializing the channel by performing a network handshake.
pub trait Session: Sync { pub trait Session: Sync {
/// Registers a new channel with the session. Performs a network handshake /// Registers a new channel with the session.
/// and starts the channel. /// Performs a network handshake and starts the channel.
// if we need to pass Self as an Arc we can do so like this: /// If we need to pass `Self` as an `Arc` we can do so like this:
// pub trait MyTrait: Send + Sync { /// ```
// async fn foo(&self, self_: Arc<dyn MyTrait>) {} /// pub trait MyTrait: Send + Sync {
// } /// async fn foo(&self, self_: Arc<dyn MyTrait>) {}
/// }
/// ```
async fn register_channel( async fn register_channel(
&self, &self,
channel: ChannelPtr, channel: ChannelPtr,
executor: Arc<Executor<'_>>, executor: Arc<Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
debug!(target: "net", "Session::register_channel() [START]"); debug!(target: "net::session::register_channel()", "[START]");
// Protocols should all be initialized but not started // Protocols should all be initialized but not started.
// We do this so that the protocols can begin receiving and buffering messages // We do this so that the protocols can begin receiving and buffering
// while the handshake protocol is ongoing. // messages while the handshake protocol is ongoing. They are currently
// They are currently in sleep mode. // in sleep mode.
let p2p = self.p2p(); let p2p = self.p2p();
let protocols = let protocols =
p2p.protocol_registry().attach(self.type_id(), channel.clone(), p2p.clone()).await; p2p.protocol_registry().attach(self.type_id(), channel.clone(), p2p.clone()).await;
@@ -144,23 +107,23 @@ pub trait Session: Sync {
handshake_task.await?; handshake_task.await?;
// Now the channel is ready // Now the channel is ready
debug!(target: "net", "Session handshake complete. Activating remaining protocols"); debug!(target: "net::session::register_channel()", "Session handshake complete");
debug!(target: "net::session::register_channel()", "Activating remaining protocols");
// Now start all the protocols // Now start all the protocols. They are responsible for managing their own
// They are responsible for managing their own lifetimes and // lifetimes and correctly selfdestructing when the channel ends.
// correctly self destructing when the channel ends.
for protocol in protocols { for protocol in protocols {
// Activate protocol
protocol.start(executor.clone()).await?; protocol.start(executor.clone()).await?;
} }
debug!(target: "net", "Session::register_channel() [END]"); debug!(target: "net::session::register_channel()", "[END]");
Ok(()) Ok(())
} }
/// Performs network handshake to initialize channel. Adds the channel to /// Performs network handshake to initialize channel. Adds the channel to
/// the list of connected channels, and prepares to remove the channel /// the list of connected channels, and prepares to remove the channel when
/// when a stop signal is received. /// a stop signal is received.
async fn perform_handshake_protocols( async fn perform_handshake_protocols(
&self, &self,
protocol_version: Arc<ProtocolVersion>, protocol_version: Arc<ProtocolVersion>,
@@ -175,17 +138,19 @@ pub trait Session: Sync {
// Add channel to p2p // Add channel to p2p
self.p2p().store(channel.clone()).await; self.p2p().store(channel.clone()).await;
// Subscribe to stop, so can remove from p2p // Subscribe to stop, so we can remove from p2p
executor.spawn(remove_sub_on_stop(self.p2p(), channel)).detach(); executor.spawn(remove_sub_on_stop(self.p2p(), channel)).detach();
// Channel is ready for use // Channel is ready for use
Ok(()) Ok(())
} }
async fn get_info(&self) -> serde_json::Value; /// Returns a pointer to the p2p network interface
/// Returns a pointer to the p2p network interface.
fn p2p(&self) -> P2pPtr; fn p2p(&self) -> P2pPtr;
fn type_id(&self) -> u32; /// Return the session bit flag for the session type
fn type_id(&self) -> SessionBitFlag;
/// Get network debug info
async fn get_info(&self) -> serde_json::Value;
} }

View File

@@ -16,27 +16,41 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::fmt; //! Outbound connections session. Manages the creation of outbound sessions.
//! Used to create an outbound session and to stop and start the session.
//!
//! Class consists of a weak pointer to the p2p interface and a vector of
//! outbound connection slots. Using a weak pointer to p2p allows us to
//! avoid circular dependencies. The vector of slots is wrapped in a mutex
//! lock. This is switched on every time we instantiate a connection slot
//! and insures that no other part of the program uses the slots at the
//! same time.
use std::collections::HashSet;
use async_std::sync::{Arc, Mutex, Weak}; use async_std::sync::{Arc, Mutex, Weak};
use async_trait::async_trait; use async_trait::async_trait;
use log::{debug, error, info, warn}; use log::{debug, error, info};
use rand::seq::SliceRandom; use serde_json::json;
use serde_json::{json, Value};
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use super::{
super::{
channel::ChannelPtr,
connector::Connector,
message::GetAddrsMessage,
p2p::{P2p, P2pPtr},
},
Session, SessionBitFlag, SESSION_OUTBOUND,
};
use crate::{ use crate::{
net::{message, transport::TransportName}, system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr},
system::{StoppableTask, StoppableTaskPtr, Subscriber, SubscriberPtr, Subscription}, util::async_util::sleep,
util::async_util,
Error, Result, Error, Result,
}; };
use super::{ pub type OutboundSessionPtr = Arc<OutboundSession>;
super::{ChannelPtr, Connector, P2p},
Session, SessionBitflag, SESSION_OUTBOUND,
};
#[derive(Clone)] #[derive(Clone)]
enum OutboundState { enum OutboundState {
@@ -45,8 +59,8 @@ enum OutboundState {
Connected, Connected,
} }
impl fmt::Display for OutboundState { impl std::fmt::Display for OutboundState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!( write!(
f, f,
"{}", "{}",
@@ -96,43 +110,44 @@ impl Default for OutboundInfo {
pub struct OutboundSession { pub struct OutboundSession {
p2p: Weak<P2p>, p2p: Weak<P2p>,
connect_slots: Mutex<Vec<StoppableTaskPtr>>, connect_slots: Mutex<Vec<StoppableTaskPtr>>,
slot_info: Mutex<Vec<OutboundInfo>>,
/// Subscriber used to signal channels processing /// Subscriber used to signal channels processing
channel_subscriber: SubscriberPtr<Result<ChannelPtr>>, channel_subscriber: SubscriberPtr<Result<ChannelPtr>>,
/// Flag to toggle channel_subscriber notifications /// Flag to toggle channel_subscriber notifications
notify: Mutex<bool>, notify: Mutex<bool>,
/// Channel debug info
slot_info: Mutex<Vec<OutboundInfo>>,
} }
impl OutboundSession { impl OutboundSession {
/// Create a new outbound session. /// Create a new outbound session.
pub fn new(p2p: Weak<P2p>) -> Arc<Self> { pub fn new(p2p: Weak<P2p>) -> OutboundSessionPtr {
Arc::new(Self { Arc::new(Self {
p2p, p2p,
connect_slots: Mutex::new(Vec::new()), connect_slots: Mutex::new(vec![]),
slot_info: Mutex::new(Vec::new()),
channel_subscriber: Subscriber::new(), channel_subscriber: Subscriber::new(),
notify: Mutex::new(false), notify: Mutex::new(false),
slot_info: Mutex::new(vec![]),
}) })
} }
/// Start the outbound session. Runs the channel connect loop. /// Start the outbound session. Runs the channel connect loop.
pub async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { pub async fn start(self: Arc<Self>, ex: Arc<Executor<'_>>) -> Result<()> {
let slots_count = self.p2p().settings().outbound_connections; let n_slots = self.p2p().settings().outbound_connections;
info!(target: "net::outbound_session", "Starting {} outbound connection slots.", slots_count); info!(target: "net::outbound_session", "[P2P] Starting {} outbound connection slots.", n_slots);
// Activate mutex lock on connection slots. // Activate mutex lock on connection slots.
let mut connect_slots = self.connect_slots.lock().await; let mut connect_slots = self.connect_slots.lock().await;
self.slot_info.lock().await.resize(slots_count as usize, Default::default()); self.slot_info.lock().await.resize(n_slots, Default::default());
for i in 0..slots_count { for i in 0..n_slots {
let task = StoppableTask::new(); let task = StoppableTask::new();
task.clone().start( task.clone().start(
self.clone().channel_connect_loop(i, executor.clone()), self.clone().channel_connect_loop(i, ex.clone()),
// Ignore stop handler // Ignore stop handler
|_| async {}, |_| async {},
Error::NetworkServiceStopped, Error::NetworkServiceStopped,
executor.clone(), ex.clone(),
); );
connect_slots.push(task); connect_slots.push(task);
@@ -141,7 +156,7 @@ impl OutboundSession {
Ok(()) Ok(())
} }
/// Stop the outbound session. /// Stops the outbound session.
pub async fn stop(&self) { pub async fn stop(&self) {
let connect_slots = &*self.connect_slots.lock().await; let connect_slots = &*self.connect_slots.lock().await;
@@ -153,115 +168,129 @@ impl OutboundSession {
/// Creates a connector object and tries to connect using it. /// Creates a connector object and tries to connect using it.
pub async fn channel_connect_loop( pub async fn channel_connect_loop(
self: Arc<Self>, self: Arc<Self>,
slot_number: u32, slot_number: usize,
executor: Arc<Executor<'_>>, ex: Arc<Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
let parent = Arc::downgrade(&self); let parent = Arc::downgrade(&self);
let connector = Connector::new(self.p2p().settings(), Arc::new(parent)); let connector = Connector::new(self.p2p().settings(), Arc::new(parent));
// Retrieve preferent outbound transports // Retrieve whitelisted outbound transports
let outbound_transports = &self.p2p().settings().outbound_transports; let transports = &self.p2p().settings().allowed_transports;
// This is the main outbound connection loop where we try to establish
// a connection in the slot. The `try_connect` function will block in
// case the connection was sucessfully established. If it fails, then
// we will wait for a defined number of seconds and try to fill the
// slot again. This function should never exit during the lifetime of
// the P2P network, as it is supposed to represent an outbound slot we
// want to fill.
// The actual connection logic and peer selection is in `try_connect`.
// If the connection is successful, `try_connect` will wait for a stop
// signal and then exit. Once it exits, we'll run `try_connect` again
// and attempt to fill the slot with another peer.
loop { loop {
match self match self.try_connect(slot_number, &connector, transports, ex.clone()).await {
.try_connect(slot_number, executor.clone(), &connector, outbound_transports) Ok(()) => {
.await info!(
{ target: "net::outbound_session",
Ok(_) => { "[P2P] Outbound slot #{} disconnected",
info!(target: "net::outbound_session", "#{} slot disconnected", slot_number) slot_number
);
} }
Err(err) => { Err(e) => {
error!(target: "net::outbound_session", "#{} slot connection failed: {}", slot_number, err) error!(
target: "net::outbound_session",
"[P2P] Outbound slot #{} connection failed: {}",
slot_number, e,
);
} }
} }
async_util::sleep(self.p2p().settings().outbound_retry_seconds).await;
} }
} }
/// Start making an outbound connection, using provided connector. /// Start making an outbound connection, using provided [`Connector`].
/// Loads a valid address then tries to connect. Once connected, /// Tries to find a valid address to connect to, otherwise does peer
/// registers the channel, removes it from the list of pending channels, /// discovery. The peer discovery loops until some peer we can connect
/// and starts sending messages across the channel, otherwise returns a network error. /// to is found. Once connected, registers the channel, removes it from
/// the list of pending channels, and starts sending messages across the
/// channel. In case of any failures, a network error is returned and the
/// main connect loop (parent of this function) will iterate again.
async fn try_connect( async fn try_connect(
&self, &self,
slot_number: u32, slot_number: usize,
executor: Arc<Executor<'_>>,
connector: &Connector, connector: &Connector,
outbound_transports: &Vec<TransportName>, transports: &[String],
ex: Arc<Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
let addr = self.load_address(slot_number).await?; debug!(
info!(target: "net::outbound_session", "#{} processing outbound [{}]", slot_number, addr); target: "net::outbound_session::try_connect()",
{ "[P2P] Finding a host to connect to for outbound slot #{}",
let info = &mut self.slot_info.lock().await[slot_number as usize]; slot_number,
info.addr = Some(addr.clone()); );
info.state = OutboundState::Pending;
}
// Check that addr transport is in configured outbound transport // Find an address to connect to. We also do peer discovery here if needed.
let addr_transport = TransportName::try_from(addr.clone())?; let addr = self.load_address(slot_number, transports).await?;
let transports = if outbound_transports.contains(&addr_transport) { info!(
vec![addr_transport] target: "net::outbound_session::try_connect()",
} else { "[P2P] Connecting outbound slot #{} [{}]",
warn!(target: "net::outbound_session", "#{} address {} transport is not in accepted outbound transports, will try with: {:?}", slot_number, addr, outbound_transports); slot_number, addr,
outbound_transports.clone() );
};
for transport in transports { match connector.connect(addr.clone()).await {
// Replace addr transport Ok(channel) => {
let mut transport_addr = addr.clone(); info!(
transport_addr.set_scheme(&transport.to_scheme())?; target: "net::outbound_session::try_connect()",
info!(target: "net::outbound_session", "#{} connecting to outbound [{}]", slot_number, transport_addr); "[P2P] Outbound slot #{} connected [{}]",
match connector.connect(transport_addr.clone()).await { slot_number, addr
Ok(channel) => { );
// Blacklist goes here
info!(target: "net::outbound_session", "#{} connected to outbound [{}]", slot_number, transport_addr);
let stop_sub = channel.subscribe_stop().await; let stop_sub =
if stop_sub.is_err() { channel.subscribe_stop().await.expect("Channel should not be stopped");
continue
}
self.register_channel(channel.clone(), executor.clone()).await?; // Register the new channel
self.register_channel(channel.clone(), ex.clone()).await?;
// Channel is now connected but not yet setup // Channel is now connected but not yet setup
// Remove pending lock since register_channel will add the channel to p2p
self.p2p().remove_pending(&addr).await;
// Remove pending lock since register_channel will add the channel to p2p dnet!(self,
self.p2p().remove_pending(&addr).await; let info = &mut self.slot_info.lock().await[slot_number];
{ info.channel = Some(channel.clone());
let info = &mut self.slot_info.lock().await[slot_number as usize]; info.state = OutboundState::Connected;
info.channel = Some(channel.clone()); );
info.state = OutboundState::Connected;
}
// Notify that channel processing has been finished // Notify that channel processing has been finished
if *self.notify.lock().await { if *self.notify.lock().await {
self.channel_subscriber.notify(Ok(channel)).await; self.channel_subscriber.notify(Ok(channel)).await;
}
// Wait for channel to close
stop_sub.unwrap().receive().await;
return Ok(())
}
Err(err) => {
error!(target: "net::outbound_session", "Unable to connect to outbound [{}]: {}", &transport_addr, err);
} }
// Wait for channel to close
stop_sub.receive().await;
return Ok(())
}
Err(e) => {
error!(
target: "net::outbound_session::try_connect()",
"[P2P] Unable to connect outbound slot #{} [{}]: {}",
slot_number, addr, e
);
} }
} }
// Remove url from hosts // At this point we failed to connect. We'll drop this peer now.
// TODO: We could potentially implement a quarantine zone for this.
self.p2p().hosts().remove(&addr).await; self.p2p().hosts().remove(&addr).await;
{ dnet!(self,
let info = &mut self.slot_info.lock().await[slot_number as usize]; let info = &mut self.slot_info.lock().await[slot_number];
info.addr = None; info.addr = None;
info.channel = None; info.channel = None;
info.state = OutboundState::Open; info.state = OutboundState::Open;
} );
// Notify that channel processing has been finished (failed) // Notify that channel processing failed
if *self.notify.lock().await { if *self.notify.lock().await {
self.channel_subscriber.notify(Err(Error::ConnectFailed)).await; self.channel_subscriber.notify(Err(Error::ConnectFailed)).await;
} }
@@ -269,94 +298,139 @@ impl OutboundSession {
Err(Error::ConnectFailed) Err(Error::ConnectFailed)
} }
/// Loops through host addresses to find a outbound address that we can /// Loops through host addresses to find an outbound address that we can
/// connect to. Checks whether address is valid by making sure it isn't /// connect to. Check whether the address is valid by making sure it isn't
/// our own inbound address, then checks whether it is already connected /// our own inbound address, then checks whether it is already connected
/// (exists) or connecting (pending). If no address was found, we try to /// (exists) or connecting (pending). If no address was found, we'll attempt
/// to discover new peers. Keeps looping until address is found that passes all checks. /// to do peer discovery and try to fill the slot again.
async fn load_address(&self, slot_number: u32) -> Result<Url> { async fn load_address(&self, slot_number: usize, transports: &[String]) -> Result<Url> {
loop { loop {
let p2p = self.p2p(); let p2p = self.p2p();
let self_inbound_addr = p2p.settings().external_addr.clone(); let retry_sleep = p2p.settings().outbound_connect_timeout;
let mut addrs; if *p2p.peer_discovery_running.lock().await {
debug!(
{ target: "net::outbound_session::load_address()",
let hosts = p2p.hosts().load_all().await; "[P2P] #{} Peer discovery active, waiting {} seconds...",
addrs = hosts; slot_number, retry_sleep,
);
sleep(retry_sleep).await;
} }
addrs.shuffle(&mut rand::thread_rng()); // Collect hosts
let mut hosts = HashSet::new();
for addr in addrs { // If transport mixing is enabled, then for example we're allowed to
if p2p.exists(&addr).await? { // use tor:// to connect to tcp:// and tor+tls:// to connect to tcp+tls://.
continue // However, **do not** mix tor:// and tcp+tls://, nor tor+tls:// and tcp://.
} let transport_mixing = self.p2p().settings().transport_mixing;
macro_rules! mix_transport {
($a:expr, $b:expr) => {
if transports.contains(&$a.to_string()) && transport_mixing {
let mut a_to_b = p2p.hosts().load_with_schemes(&[$b.to_string()]).await;
for addr in a_to_b.iter_mut() {
addr.set_scheme($a).unwrap();
hosts.insert(addr.clone());
}
}
};
}
mix_transport!("tor", "tcp");
mix_transport!("tor+tls", "tcp+tls");
mix_transport!("nym", "tcp");
mix_transport!("nym+tls", "tcp+tls");
// Check if address is in peers list // And now the actual requested transports
if p2p.settings().peers.contains(&addr) { for addr in p2p.hosts().load_with_schemes(transports).await {
continue hosts.insert(addr);
}
// Obtain a lock on this address to prevent duplicate connections
if !p2p.add_pending(addr.clone()).await {
continue
}
if self_inbound_addr.contains(&addr) {
continue
}
return Ok(addr)
} }
// Peer discovery // Try to find an unused host in the set.
if p2p.settings().peer_discovery { for host in &hosts {
debug!(target: "net::outbound_session", "#{} No available address found, entering peer discovery mode.", slot_number); // Check if we already have this connection established
self.peer_discovery(slot_number).await?; if p2p.exists(host).await {
debug!(target: "net::outbound_session", "#{} Discovery mode ended.", slot_number); continue
}
// Check if we already have this configured as a manual peer
if p2p.settings().peers.contains(host) {
continue
}
// Obtain a lock on this address to prevent duplicate connection
if !p2p.add_pending(host).await {
continue
}
dnet!(self,
let info = &mut self.slot_info.lock().await[slot_number];
info.addr = Some(host.clone());
info.state = OutboundState::Pending;
);
return Ok(host.clone())
} }
// Sleep and then retry // We didn't find a host to connect to, let's try to find more peers.
debug!(target: "net::outbound_session", "Retrying connect slot #{}", slot_number); info!(
async_util::sleep(p2p.settings().outbound_retry_seconds).await; target: "net::outbound_session::load_address()",
"[P2P] Outbound #{}: No peers found. Starting peer discovery...",
slot_number,
);
// NOTE: A design decision here is to do a sleep inside peer_discovery()
// so that there's a certain period (outbound_connect_timeout) of time
// to send the GetAddr, receive Addrs, and sort things out. By sleeping
// inside peer_discovery, it will block here in the slot sessions, while
// other slots can keep trying to find hosts. This is also why we sleep
// in the beginning of this loop if peer discovery is currently active.
self.peer_discovery(slot_number).await;
} }
} }
/// Try to find new peers to update available hosts. /// Activate peer discovery if not active already. This will loop through all
async fn peer_discovery(&self, slot_number: u32) -> Result<()> { /// connected P2P channels and send out a `GetAddrs` message to request more
// Check that another slot(thread) already tries to update hosts /// peers. Other parts of the P2P stack will then handle the incoming addresses
/// and place them in the hosts list.
/// This function will also sleep [`Settings:outbound_connect_timeout`] seconds
/// after broadcasting in order to let the P2P stack receive and work through
/// the addresses it is expecting.
async fn peer_discovery(&self, slot_number: usize) {
let p2p = self.p2p(); let p2p = self.p2p();
if !p2p.clone().start_discovery().await {
debug!(target: "net::outbound_session", "#{} P2P already on discovery mode.", slot_number); if *p2p.peer_discovery_running.lock().await {
return Ok(()) info!(
target: "net::outbound_session::peer_discovery()",
"[P2P] Outbound #{}: Peer discovery already active",
slot_number,
);
return
} }
debug!(target: "net::outbound_session", "#{} Discovery mode started.", slot_number); info!(
target: "net::outbound_session::peer_discovery()",
"[P2P] Outbound #{}: Started peer discovery",
slot_number,
);
*p2p.peer_discovery_running.lock().await = true;
// Getting a random connected channel to ask for peers // Broadcast the GetAddrs message to all active channels
let channel = match p2p.clone().random_channel().await { let get_addrs = GetAddrsMessage { max: p2p.settings().outbound_connections as u32 };
Some(c) => c, info!(
None => { target: "net::outbound_session::peer_discovery()",
debug!(target: "net::outbound_session", "#{} No peers found.", slot_number); "[P2P] Outbound #{}: Broadcasting GetAddrs across active channels",
p2p.clone().stop_discovery().await; slot_number,
return Ok(()) );
} p2p.broadcast(&get_addrs).await;
};
// Ask peer // Now sleep to let the GetAddrs propagate, and hopefully
debug!(target: "net::outbound_session", "#{} Asking peer: {}", slot_number, channel.address()); // in the meantime we'll get some peers.
let get_addr_msg = message::GetAddrsMessage {}; debug!(
channel.send(get_addr_msg).await?; target: "net::outbound_session::peer_discovery()",
"[P2P] Outbound #{}: Sleeping {} seconds",
p2p.stop_discovery().await; slot_number, p2p.settings().outbound_connect_timeout,
);
Ok(()) sleep(p2p.settings().outbound_connect_timeout).await;
} *p2p.peer_discovery_running.lock().await = false;
/// Subscribe to a channel.
pub async fn subscribe_channel(&self) -> Subscription<Result<ChannelPtr>> {
self.channel_subscriber.clone().subscribe().await
} }
/// Enable channel_subscriber notifications. /// Enable channel_subscriber notifications.
@@ -372,14 +446,22 @@ impl OutboundSession {
#[async_trait] #[async_trait]
impl Session for OutboundSession { impl Session for OutboundSession {
fn p2p(&self) -> P2pPtr {
self.p2p.upgrade().unwrap()
}
fn type_id(&self) -> SessionBitFlag {
SESSION_OUTBOUND
}
async fn get_info(&self) -> serde_json::Value { async fn get_info(&self) -> serde_json::Value {
let mut slots = Vec::new(); let mut slots = vec![];
for info in &*self.slot_info.lock().await { for info in &*self.slot_info.lock().await {
slots.push(info.get_info().await); slots.push(info.get_info().await);
} }
let hosts = self.p2p().hosts().load_all().await; let hosts = self.p2p().hosts().load_all().await;
let addrs: Vec<Value> = let addrs: Vec<serde_json::Value> =
hosts.iter().map(|addr| serde_json::Value::String(addr.to_string())).collect(); hosts.iter().map(|addr| serde_json::Value::String(addr.to_string())).collect();
json!({ json!({
@@ -387,12 +469,4 @@ impl Session for OutboundSession {
"hosts": serde_json::Value::Array(addrs), "hosts": serde_json::Value::Array(addrs),
}) })
} }
fn p2p(&self) -> Arc<P2p> {
self.p2p.upgrade().unwrap()
}
fn type_id(&self) -> SessionBitflag {
SESSION_OUTBOUND
}
} }

View File

@@ -16,6 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
//! Seed sync session creates a connection to the seed nodes specified in settings.
//! A new seed sync session is created every time we call [`P2p::start()`]. The
//! seed sync session loops through all the configured seeds and tries to connect
//! to them using a [`Connector`]. Seed sync either connects successfully, fails
//! with an error, or times out.
//!
//! If a seed node connects successfully, it runs a version exchange protocol,
//! stores the channel in the p2p list of channels, and discoonnects, removing
//! the channel from the channel list.
//!
//! The channel is registered using the [`Session::register_channel()`] trait
//! method. This invokes the Protocol Registry method `attach()`. Usually this
//! returns a list of protocols that we loop through and start. In this case,
//! `attach()` uses the bitflag selector to identify seed sessions and exclude
//! them.
//!
//! The version exchange occurs inside `register_channel()`. We create a handshake
//! task that runs the version exchange with the `perform_handshake_protocols()`
//! function. This runs the version exchange protocol, stores the channel in the
//! p2p list of channels, and subscribes to a stop signal.
use std::time::Duration; use std::time::Duration;
use async_std::{ use async_std::{
@@ -24,161 +45,161 @@ use async_std::{
}; };
use async_trait::async_trait; use async_trait::async_trait;
use futures::future::join_all; use futures::future::join_all;
use log::*; use log::{debug, error, info, warn};
use serde_json::json;
use smol::Executor; use smol::Executor;
use url::Url; use url::Url;
use super::{
super::{connector::Connector, p2p::P2p},
P2pPtr, Session, SessionBitFlag, SESSION_SEED,
};
use crate::Result; use crate::Result;
use super::{ pub type SeedSyncSessionPtr = Arc<SeedSyncSession>;
super::{Connector, P2p},
Session, SessionBitflag, SESSION_SEED,
};
/// Defines seed connections session. /// Defines seed connections session
pub struct SeedSyncSession { pub struct SeedSyncSession {
p2p: Weak<P2p>, p2p: Weak<P2p>,
} }
impl SeedSyncSession { impl SeedSyncSession {
/// Create a new seed sync session instance. /// Create a new seed sync session instance
pub fn new(p2p: Weak<P2p>) -> Arc<Self> { pub fn new(p2p: Weak<P2p>) -> SeedSyncSessionPtr {
Arc::new(Self { p2p }) Arc::new(Self { p2p })
} }
/// Start the seed sync session. Creates a new task for every seed connection and /// Start the seed sync session. Creates a new task for every seed
/// starts the seed on each task. /// connection and starts the seed on each task.
pub async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> { pub async fn start(self: Arc<Self>, executor: Arc<Executor<'_>>) -> Result<()> {
debug!(target: "net::seedsync_session", "SeedSyncSession::start() [START]"); debug!(target: "net::session::seedsync_session", "SeedSyncSession::start() [START]");
let settings = self.p2p().settings(); let settings = self.p2p().settings();
if settings.seeds.is_empty() { if settings.seeds.is_empty() {
warn!(target: "net::seedsync_session", "Skipping seed sync process since no seeds are configured."); warn!(
// Store external addresses in hosts explicitly target: "net::session::seedsync_session",
if !settings.external_addr.is_empty() { "[P2P] Skipping seed sync process since no seeds are configured.",
self.p2p().hosts().store(settings.external_addr.clone()).await );
}
return Ok(()) return Ok(())
} }
// if cached addresses then quit // Gather tasks so we can execute concurrently
let mut tasks = Vec::with_capacity(settings.seeds.len());
let mut tasks = Vec::new(); let conn_timeout = Duration::from_secs(settings.seed_query_timeout);
// This loops through all the seeds and tries to start them.
// If the seed_query_timeout_seconds times out before they are finished,
// it will return an error.
for (i, seed) in settings.seeds.iter().enumerate() { for (i, seed) in settings.seeds.iter().enumerate() {
let ex2 = executor.clone(); let ex_ = executor.clone();
let self2 = self.clone(); let self_ = self.clone();
let sett2 = settings.clone();
tasks.push(async move {
let task = self2.clone().start_seed(i, seed.clone(), ex2.clone());
let result = tasks.push(async move {
timeout(Duration::from_secs(sett2.seed_query_timeout_seconds.into()), task) let task = self_.clone().start_seed(i, seed.clone(), ex_.clone());
.await; let result = timeout(conn_timeout, task).await;
match result { match result {
Ok(t) => match t { Ok(t) => match t {
Ok(()) => { Ok(()) => {
info!(target: "net::seedsync_session", "Seed #{} connected successfully", i) info!(
target: "net::session::seedsync_session",
"[P2P] Seed #{} connected successfully", i,
);
} }
Err(err) => { Err(err) => {
warn!(target: "net::seedsync_session", "Seed #{} failed for reason {}", i, err) warn!(
target: "net::session::seedsync_session",
"[P2P] Seed #{} connection failed: {}", i, err,
);
} }
}, },
Err(_err) => error!(target: "net::seedsync_session", "Seed #{} timed out", i), Err(_) => {
error!(
target: "net::session::seedsync_session",
"[P2P] Seed #{} timed out", i
);
}
} }
}); });
} }
// Poll concurrently
join_all(tasks).await; join_all(tasks).await;
// Seed process complete // Seed process complete
if self.p2p().hosts().is_empty().await { if self.p2p().hosts().is_empty().await {
warn!(target: "net::seedsync_session", "Hosts pool still empty after seeding"); warn!(target: "net::session::seedsync_session", "[P2P] Hosts pool empty after seeding");
} }
debug!(target: "net::seedsync_session", "SeedSyncSession::start() [END]"); debug!(target: "net::session::seedsync_session", "SeedSyncSession::start() [END]");
Ok(()) Ok(())
} }
/// Connects to a seed socket address. /// Connects to a seed socket address
async fn start_seed( async fn start_seed(
self: Arc<Self>, self: Arc<Self>,
seed_index: usize, seed_index: usize,
seed: Url, seed: Url,
executor: Arc<Executor<'_>>, ex: Arc<Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
debug!(target: "net::seedsync_session", "SeedSyncSession::start_seed(i={}) [START]", seed_index); debug!(
let (_hosts, settings) = { target: "net::session::seedsync_session", "SeedSyncSession::start_seed(i={}) [START]",
let p2p = self.p2p.upgrade().unwrap(); seed_index
(p2p.hosts(), p2p.settings()) );
};
let settings = self.p2p.upgrade().unwrap().settings();
let parent = Arc::downgrade(&self); let parent = Arc::downgrade(&self);
let connector = Connector::new(settings.clone(), Arc::new(parent)); let connector = Connector::new(settings.clone(), Arc::new(parent));
match connector.connect(seed.clone()).await { match connector.connect(seed.clone()).await {
Ok(channel) => { Ok(ch) => {
// Blacklist goes here info!(
target: "net::session::seedsync_session",
"[P2P] Connected seed #{} [{}]", seed_index, seed,
);
info!(target: "net::seedsync_session", "Connected seed #{} [{}]", seed_index, seed); if let Err(e) = self.clone().register_channel(ch.clone(), ex.clone()).await {
warn!(
if let Err(err) = target: "net::session::seedsync_session",
self.clone().register_channel(channel.clone(), executor.clone()).await "[P2P] Failure during sync seed session #{} [{}]: {}",
{ seed_index, seed, e,
warn!(target: "net::seedsync_session", "Failure during seed sync session #{} [{}]: {}", seed_index, seed, err); );
} }
info!(target: "net::seedsync_session", "Disconnecting from seed #{} [{}]", seed_index, seed); info!(
channel.stop().await; target: "net::session::seedsync_session",
"[P2P] Disconnecting from seed #{} [{}]",
debug!(target: "net::seedsync_session", "SeedSyncSession::start_seed(i={}) [END]", seed_index); seed_index, seed,
Ok(()) );
ch.stop().await;
} }
Err(err) => {
warn!(target: "net::seedsync_session", "Failure contacting seed #{} [{}]: {}", seed_index, seed, err); Err(e) => {
Err(err) warn!(
target: "net::session:seedsync_session",
"[P2P] Failure contacting seed #{} [{}]: {}",
seed_index, seed, e
);
return Err(e)
} }
} }
debug!(
target: "net::session::seedsync_session",
"SeedSyncSession::start_seed(i={}) [END]",
seed_index
);
Ok(())
} }
// Starts keep-alive messages and seed protocol.
/*async fn attach_protocols(
self: Arc<Self>,
channel: ChannelPtr,
hosts: HostsPtr,
settings: SettingsPtr,
executor: Arc<Executor<'_>>,
) -> Result<()> {
let protocol_ping = ProtocolPing::new(channel.clone(), self.p2p());
protocol_ping.start(executor.clone()).await;
let protocol_seed = ProtocolSeed::new(channel.clone(), hosts, settings.clone());
// This will block until seed process is complete
protocol_seed.start(executor.clone()).await?;
channel.stop().await;
Ok(())
}*/
} }
#[async_trait] #[async_trait]
impl Session for SeedSyncSession { impl Session for SeedSyncSession {
async fn get_info(&self) -> serde_json::Value { fn p2p(&self) -> P2pPtr {
json!({
"key": 110
})
}
fn p2p(&self) -> Arc<P2p> {
self.p2p.upgrade().unwrap() self.p2p.upgrade().unwrap()
} }
fn type_id(&self) -> SessionBitflag { fn type_id(&self) -> SessionBitFlag {
SESSION_SEED SESSION_SEED
} }
async fn get_info(&self) -> serde_json::Value {
todo!()
}
} }

View File

@@ -16,210 +16,171 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::Arc; use async_std::sync::Arc;
use serde::Deserialize;
use structopt::StructOpt; use structopt::StructOpt;
use structopt_toml::StructOptToml;
use url::Url; use url::Url;
use crate::net::transport::TransportName; /// Atomic pointer to network settings
/// Atomic pointer to network settings.
pub type SettingsPtr = Arc<Settings>; pub type SettingsPtr = Arc<Settings>;
/// Default settings for the network. Can be manually configured. /// P2P network settings. The scope of this is a P2P network instance
#[derive(Clone, Debug)] /// configured by the library user.
#[derive(Debug, Clone)]
pub struct Settings { pub struct Settings {
/// P2P accept addresses node listens to for inbound connections /// Only used for debugging, compromises privacy when set
pub inbound: Vec<Url>, pub node_id: String,
/// Outbound connection slots number /// P2P accept addresses the instance listens on for inbound connections
pub outbound_connections: u32, pub inbound_addrs: Vec<Url>,
/// Manual connections retry limit, 0 for forever looping /// P2P external addresses the instance advertises so other peers can
pub manual_attempt_limit: u32, /// reach us and connect to us, as long as inbound addrs are configured
/// Seed connection establishment timeout pub external_addrs: Vec<Url>,
pub seed_query_timeout_seconds: u32,
/// Connection establishment timeout
pub connect_timeout_seconds: u32,
/// Exchange versions (handshake) timeout
pub channel_handshake_seconds: u32,
/// Ping-pong exhange execution interval
pub channel_heartbeat_seconds: u32,
/// Try to fill an outbound slot interval
pub outbound_retry_seconds: u64,
/// P2P external addresses node advertises so other peers can reach us
/// and connect to us, as long us inbound addresses are also configured
pub external_addr: Vec<Url>,
/// Peer nodes to manually connect to /// Peer nodes to manually connect to
pub peers: Vec<Url>, pub peers: Vec<Url>,
/// Seed nodes to connect to for peers retrieval and/or advertising our own /// Seed nodes to connect to for peer discovery and/or adversising our
/// external address /// own external addresses
pub seeds: Vec<Url>, pub seeds: Vec<Url>,
/// Only used for debugging. Compromises privacy when set. /// Application version, used for convenient protocol matching
pub node_id: String, pub app_version: semver::Version,
/// Application version, used for verification between peers /// Whitelisted network transports for outbound connections
pub app_version: Option<String>, pub allowed_transports: Vec<String>,
/// Prefered transports for outbound connections /// Allow transport mixing (e.g. Tor would be allowed to connect to `tcp://`)
pub outbound_transports: Vec<TransportName>, pub transport_mixing: bool,
/// Outbound connection slots number, this many connections will be
/// attempted. (This does not include manual connections)
pub outbound_connections: usize,
/// Manual connections retry limit, 0 for forever looping
pub manual_attempt_limit: usize,
/// Seed connection establishment timeout (in seconds)
pub seed_query_timeout: u64,
/// Outbound connection establishment timeout (in seconds)
pub outbound_connect_timeout: u64,
/// Exchange versions (handshake) timeout (in seconds)
pub channel_handshake_timeout: u64,
/// Ping-pong exchange execution interval (in seconds)
pub channel_heartbeat_interval: u64,
/// Allow localnet hosts /// Allow localnet hosts
pub localnet: bool, pub localnet: bool,
/// Enable peer discovery
pub peer_discovery: bool,
/// Enable channel logging
pub channel_log: bool,
} }
impl Default for Settings { impl Default for Settings {
fn default() -> Self { fn default() -> Self {
let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.0.0");
let app_version = semver::Version::parse(version).unwrap();
Self { Self {
inbound: Vec::new(), node_id: String::new(),
inbound_addrs: vec![],
external_addrs: vec![],
peers: vec![],
seeds: vec![],
app_version,
allowed_transports: vec![],
transport_mixing: true,
outbound_connections: 0, outbound_connections: 0,
manual_attempt_limit: 0, manual_attempt_limit: 0,
seed_query_timeout_seconds: 8, seed_query_timeout: 30,
connect_timeout_seconds: 10, outbound_connect_timeout: 15,
channel_handshake_seconds: 4, channel_handshake_timeout: 4,
channel_heartbeat_seconds: 10, channel_heartbeat_interval: 10,
outbound_retry_seconds: 20,
external_addr: Vec::new(),
peers: Vec::new(),
seeds: Vec::new(),
node_id: String::new(),
app_version: Some(option_env!("CARGO_PKG_VERSION").unwrap_or("").to_string()),
outbound_transports: get_outbound_transports(vec![]),
localnet: false, localnet: false,
peer_discovery: true,
channel_log: false,
} }
} }
} }
// The following is used so we can have P2P settings configurable
// from TOML files.
/// Defines the network settings. /// Defines the network settings.
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)] #[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
#[structopt()] #[structopt()]
pub struct SettingsOpt { pub struct SettingsOpt {
/// P2P accept addresses node listens to for inbound connections /// P2P accept address node listens to for inbound connections
#[serde(default)] #[serde(default)]
#[structopt(long = "accept")] #[structopt(long = "accept")]
pub inbound: Vec<Url>, pub inbound: Vec<Url>,
/// Outbound connection slots number /// Outbound connection slots number
#[structopt(long = "slots")] #[structopt(long = "slots")]
pub outbound_connections: Option<u32>, pub outbound_connections: Option<usize>,
/// P2P external addresses node advertises so other peers can reach us /// P2P external addresses node advertises so other peers can
/// and connect to us, as long us inbound addresses are also configured /// reach us and connect to us, as long as inbound addresses
/// are also configured
#[serde(default)] #[serde(default)]
#[structopt(long)] #[structopt(long)]
pub external_addr: Vec<Url>, pub external_addrs: Vec<Url>,
/// Peer nodes to manually connect to /// Peer nodes to manually connect to
#[serde(default)] #[serde(default)]
#[structopt(long)] #[structopt(long)]
pub peers: Vec<Url>, pub peers: Vec<Url>,
/// Seed nodes to connect to for peers retrieval and/or advertising our own /// Seed nodes to connect to for peers retrieval and/or
/// external address /// advertising our own external addresses
#[serde(default)] #[serde(default)]
#[structopt(long)] #[structopt(long)]
pub seeds: Vec<Url>, pub seeds: Vec<Url>,
/// Manual connections retry limit /// Manual connections retry limit
#[structopt(skip)] #[structopt(skip)]
pub manual_attempt_limit: Option<u32>, pub manual_attempt_limit: Option<usize>,
/// Seed connection establishment timeout /// Seed connection establishment timeout in seconds
#[structopt(skip)] #[structopt(skip)]
pub seed_query_timeout_seconds: Option<u32>, pub seed_query_timeout: Option<u64>,
/// Connection establishment timeout /// Connection establishment timeout in seconds
#[structopt(skip)] #[structopt(skip)]
pub connect_timeout_seconds: Option<u32>, pub outbound_connect_timeout: Option<u64>,
/// Exchange versions (handshake) timeout /// Exchange versions (handshake) timeout in seconds
#[structopt(skip)] #[structopt(skip)]
pub channel_handshake_seconds: Option<u32>, pub channel_handshake_timeout: Option<u64>,
/// Ping-pong exhange execution interval /// Ping-pong exchange execution interval in seconds
#[structopt(skip)] #[structopt(skip)]
pub channel_heartbeat_seconds: Option<u32>, pub channel_heartbeat_interval: Option<u64>,
/// Try to fill an outbound slot interval
#[structopt(skip)]
pub outbound_retry_seconds: Option<u64>,
/// Only used for debugging. Compromises privacy when set. /// Only used for debugging. Compromises privacy when set.
#[serde(default)] #[serde(default)]
#[structopt(skip)] #[structopt(skip)]
pub node_id: String, pub node_id: String,
/// Application version, used for verification between peers /// Preferred transports for outbound connections
#[serde(default)]
#[structopt(skip)]
pub app_version: Option<String>,
/// Prefered transports for outbound connections
#[serde(default)] #[serde(default)]
#[structopt(long = "transports")] #[structopt(long = "transports")]
pub outbound_transports: Vec<String>, pub allowed_transports: Vec<String>,
#[structopt(long)]
pub transport_mixing: bool,
/// Allow localnet hosts /// Allow localnet hosts
#[serde(default)] #[serde(default)]
#[structopt(long)] #[structopt(long)]
pub localnet: bool, pub localnet: bool,
/// Enable peer discovery
#[serde(default = "default_as_true")]
#[structopt(long)]
pub peer_discovery: bool,
/// Enable channel logging
#[serde(default)]
#[structopt(long)]
pub channel_log: bool,
} }
impl From<SettingsOpt> for Settings { impl From<SettingsOpt> for Settings {
fn from(settings_opt: SettingsOpt) -> Self { fn from(opt: SettingsOpt) -> Self {
let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.0.0");
let app_version = semver::Version::parse(version).unwrap();
Self { Self {
inbound: settings_opt.inbound, node_id: opt.node_id,
outbound_connections: settings_opt.outbound_connections.unwrap_or(0), inbound_addrs: opt.inbound,
manual_attempt_limit: settings_opt.manual_attempt_limit.unwrap_or(0), external_addrs: opt.external_addrs,
seed_query_timeout_seconds: settings_opt.seed_query_timeout_seconds.unwrap_or(8), peers: opt.peers,
connect_timeout_seconds: settings_opt.connect_timeout_seconds.unwrap_or(10), seeds: opt.seeds,
channel_handshake_seconds: settings_opt.channel_handshake_seconds.unwrap_or(4), app_version,
channel_heartbeat_seconds: settings_opt.channel_heartbeat_seconds.unwrap_or(10), allowed_transports: opt.allowed_transports,
outbound_retry_seconds: settings_opt.outbound_retry_seconds.unwrap_or(1200), transport_mixing: opt.transport_mixing,
external_addr: settings_opt.external_addr, outbound_connections: opt.outbound_connections.unwrap_or(0),
peers: settings_opt.peers, manual_attempt_limit: opt.manual_attempt_limit.unwrap_or(0),
seeds: settings_opt.seeds, seed_query_timeout: opt.seed_query_timeout.unwrap_or(30),
node_id: settings_opt.node_id, outbound_connect_timeout: opt.outbound_connect_timeout.unwrap_or(15),
app_version: settings_opt.app_version, channel_handshake_timeout: opt.channel_handshake_timeout.unwrap_or(4),
outbound_transports: get_outbound_transports(settings_opt.outbound_transports), channel_heartbeat_interval: opt.channel_heartbeat_interval.unwrap_or(10),
localnet: settings_opt.localnet, localnet: opt.localnet,
peer_discovery: settings_opt.peer_discovery,
channel_log: settings_opt.channel_log,
} }
} }
} }
/// Auxiliary function to convert outbound transport `Vec<String>`
/// to `Vec<TransportName>`, using defaults if empty.
pub fn get_outbound_transports(opt_outbound_transports: Vec<String>) -> Vec<TransportName> {
let mut outbound_transports = vec![];
for transport in opt_outbound_transports {
let transport_name = TransportName::try_from(transport.as_str()).unwrap();
outbound_transports.push(transport_name);
}
if outbound_transports.is_empty() {
let tls = TransportName::Tcp(Some("tls".into()));
outbound_transports.push(tls);
}
outbound_transports
}
/// Auxiliary function to set serde bool value to true.
fn default_as_true() -> bool {
true
}

View File

@@ -16,120 +16,275 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{net::SocketAddr, time::Duration}; use std::time::Duration;
use async_trait::async_trait; use async_trait::async_trait;
use futures::prelude::*; use futures::{AsyncRead, AsyncWrite};
use futures_rustls::{TlsAcceptor, TlsStream};
use url::Url; use url::Url;
use crate::Result; use crate::{Error, Result};
mod upgrade_tls; /// TLS Upgrade Mechanism
pub use upgrade_tls::TlsUpgrade; pub(crate) mod tls;
mod tcp; #[cfg(feature = "p2p-transport-tcp")]
pub use tcp::TcpTransport; /// TCP Transport
pub(crate) mod tcp;
mod tor; #[cfg(feature = "p2p-transport-tor")]
pub use tor::TorTransport; /// Tor transport
pub(crate) mod tor;
mod unix; #[cfg(feature = "p2p-transport-nym")]
pub use unix::UnixTransport; /// Nym transport
pub(crate) mod nym;
mod nym; /// Dialer variants
pub use nym::NymTransport; #[derive(Debug, Clone)]
pub enum DialerVariant {
#[cfg(feature = "p2p-transport-tcp")]
/// Plain TCP
Tcp(tcp::TcpDialer),
/// A helper function to convert SocketAddr to Url and add scheme #[cfg(feature = "p2p-transport-tcp")]
pub(crate) fn socket_addr_to_url(addr: SocketAddr, scheme: &str) -> Result<Url> { /// TCP with TLS
let url = Url::parse(&format!("{}://{}", scheme, addr))?; TcpTls(tcp::TcpDialer),
Ok(url)
#[cfg(feature = "p2p-transport-tor")]
/// Tor
Tor(tor::TorDialer),
#[cfg(feature = "p2p-transport-tor")]
/// Tor with TLS
TorTls(tor::TorDialer),
#[cfg(feature = "p2p-transport-nym")]
/// Nym
Nym(nym::NymDialer),
#[cfg(feature = "p2p-transport-nym")]
/// Nym with TLS
NymTls(nym::NymDialer),
} }
/// Used as wrapper for stream used by Transport trait /// Listener variants
pub trait TransportStream: AsyncWrite + AsyncRead + Unpin + Send + Sync {} #[derive(Debug, Clone)]
pub enum ListenerVariant {
#[cfg(feature = "p2p-transport-tcp")]
/// Plain TCP
Tcp(tcp::TcpListener),
/// Used as wrapper for listener used by Transport trait #[cfg(feature = "p2p-transport-tcp")]
#[async_trait] /// TCP with TLS
pub trait TransportListener: Send + Sync + Unpin { TcpTls(tcp::TcpListener),
async fn next(&self) -> Result<(Box<dyn TransportStream>, Url)>;
} }
#[derive(Clone, Debug, PartialEq, Eq)] /// A dialer that is able to transparently operate over arbitrary transports.
pub enum TransportName { pub struct Dialer {
Tcp(Option<String>), /// The endpoint to connect to
Tor(Option<String>), endpoint: Url,
Nym(Option<String>), /// The dialer variant (transport protocol)
Unix, variant: DialerVariant,
} }
impl TransportName { impl Dialer {
pub fn to_scheme(&self) -> String { /// Instantiate a new [`Dialer`] with the given [`Url`].
match self { /// Must contain a scheme, host string, and a port.
Self::Tcp(None) => "tcp".into(), pub async fn new(endpoint: Url) -> Result<Self> {
Self::Tcp(Some(opt)) => format!("tcp+{}", opt), if endpoint.host_str().is_none() || endpoint.port().is_none() {
Self::Tor(None) => "tor".into(), return Err(Error::InvalidDialerScheme)
Self::Tor(Some(opt)) => format!("tor+{}", opt), }
Self::Nym(None) => "nym".into(),
Self::Nym(Some(opt)) => format!("nym+{}", opt), match endpoint.scheme().to_lowercase().as_str() {
Self::Unix => "unix".into(), #[cfg(feature = "p2p-transport-tcp")]
"tcp" => {
// Build a TCP dialer
let variant = tcp::TcpDialer::new(None).await?;
let variant = DialerVariant::Tcp(variant);
Ok(Self { endpoint, variant })
}
#[cfg(feature = "p2p-transport-tcp")]
"tcp+tls" => {
// Build a TCP dialer wrapped with TLS
let variant = tcp::TcpDialer::new(None).await?;
let variant = DialerVariant::TcpTls(variant);
Ok(Self { endpoint, variant })
}
#[cfg(feature = "p2p-transport-tor")]
"tor" => {
// Build a Tor dialer
let variant = tor::TorDialer::new().await?;
let variant = DialerVariant::Tor(variant);
Ok(Self { endpoint, variant })
}
#[cfg(feature = "p2p-transport-tor")]
"tor+tls" => {
// Build a Tor dialer wrapped with TLS
let variant = tor::TorDialer::new().await?;
let variant = DialerVariant::TorTls(variant);
Ok(Self { endpoint, variant })
}
#[cfg(feature = "p2p-transport-nym")]
"nym" => {
// Build a Nym dialer
let variant = nym::NymDialer::new().await?;
let variant = DialerVariant::Nym(variant);
Ok(Self { endpoint, variant })
}
#[cfg(feature = "p2p-transport-nym")]
"nym+tls" => {
// Build a Nym dialer wrapped with TLS
let variant = nym::NymDialer::new().await?;
let variant = DialerVariant::NymTls(variant);
Ok(Self { endpoint, variant })
}
x => Err(Error::UnsupportedTransport(x.to_string())),
} }
} }
}
impl TryFrom<&str> for TransportName { /// Dial an instantiated [`Dialer`]. This creates a connection and returns a stream.
type Error = crate::Error; pub async fn dial(&self, timeout: Option<Duration>) -> Result<Box<dyn PtStream>> {
match &self.variant {
#[cfg(feature = "p2p-transport-tcp")]
DialerVariant::Tcp(dialer) => {
// NOTE: sockaddr here is an array, can contain both ipv4 and ipv6
let sockaddr = self.endpoint.socket_addrs(|| None)?;
let stream = dialer.do_dial(sockaddr[0], timeout).await?;
Ok(Box::new(stream))
}
fn try_from(scheme: &str) -> Result<Self> { #[cfg(feature = "p2p-transport-tcp")]
let transport_name = match scheme { DialerVariant::TcpTls(dialer) => {
"tcp" => Self::Tcp(None), let sockaddr = self.endpoint.socket_addrs(|| None)?;
"tcp+tls" | "tls" => Self::Tcp(Some("tls".into())), let stream = dialer.do_dial(sockaddr[0], timeout).await?;
"tor" => Self::Tor(None), let tlsupgrade = tls::TlsUpgrade::new();
"tor+tls" => Self::Tor(Some("tls".into())), let stream = tlsupgrade.upgrade_dialer_tls(stream).await?;
"nym" => Self::Nym(None), Ok(Box::new(stream))
"nym+tls" => Self::Nym(Some("tls".into())), }
"unix" => Self::Unix,
n => return Err(crate::Error::UnsupportedTransport(n.into())), #[cfg(feature = "p2p-transport-tor")]
}; DialerVariant::Tor(dialer) => {
Ok(transport_name) let host = self.endpoint.host_str().unwrap();
let port = self.endpoint.port().unwrap();
let stream = dialer.do_dial(host, port, timeout).await?;
Ok(Box::new(stream))
}
#[cfg(feature = "p2p-transport-tor")]
DialerVariant::TorTls(dialer) => {
let host = self.endpoint.host_str().unwrap();
let port = self.endpoint.port().unwrap();
let stream = dialer.do_dial(host, port, timeout).await?;
let tlsupgrade = tls::TlsUpgrade::new();
let stream = tlsupgrade.upgrade_dialer_tls(stream).await?;
Ok(Box::new(stream))
}
#[cfg(feature = "p2p-transport-nym")]
DialerVariant::Nym(dialer) => {
todo!();
}
#[cfg(feature = "p2p-transport-nym")]
DialerVariant::NymTls(dialer) => {
todo!();
}
}
}
/// Return a reference to the `Dialer` endpoint
pub fn endpoint(&self) -> &Url {
&self.endpoint
} }
} }
impl TryFrom<Url> for TransportName { /// A listener that is able to transparently listen over arbitrary transports.
type Error = crate::Error; pub struct Listener {
/// The address to open the listener on
endpoint: Url,
/// The listener variant (transport protocol)
variant: ListenerVariant,
}
fn try_from(url: Url) -> Result<Self> { impl Listener {
Self::try_from(url.scheme()) /// Instantiate a new [`Listener`] with the given [`Url`].
/// Must contain a scheme, host string, and a port.
pub async fn new(endpoint: Url) -> Result<Self> {
if endpoint.host_str().is_none() || endpoint.port().is_none() {
return Err(Error::InvalidListenerScheme)
}
match endpoint.scheme().to_lowercase().as_str() {
#[cfg(feature = "p2p-transport-tcp")]
"tcp" => {
// Build a TCP listener
let variant = tcp::TcpListener::new(1024).await?;
let variant = ListenerVariant::Tcp(variant);
Ok(Self { endpoint, variant })
}
#[cfg(feature = "p2p-transport-tcp")]
"tcp+tls" => {
// Build a TCP listener wrapped with TLS
let variant = tcp::TcpListener::new(1024).await?;
let variant = ListenerVariant::TcpTls(variant);
Ok(Self { endpoint, variant })
}
x => Err(Error::UnsupportedTransport(x.to_string())),
}
}
/// Listen on an instantiated [`Listener`].
/// This will open a socket and return the listener.
pub async fn listen(&self) -> Result<Box<dyn PtListener>> {
match &self.variant {
#[cfg(feature = "p2p-transport-tcp")]
ListenerVariant::Tcp(listener) => {
let sockaddr = self.endpoint.socket_addrs(|| None)?;
let l = listener.do_listen(sockaddr[0]).await?;
Ok(Box::new(l))
}
#[cfg(feature = "p2p-transport-tcp")]
ListenerVariant::TcpTls(listener) => {
let sockaddr = self.endpoint.socket_addrs(|| None)?;
let l = listener.do_listen(sockaddr[0]).await?;
let tlsupgrade = tls::TlsUpgrade::new();
let l = tlsupgrade.upgrade_listener_tcp_tls(l).await?;
Ok(Box::new(l))
}
}
}
pub fn endpoint(&self) -> &Url {
&self.endpoint
} }
} }
/// The `Transport` trait serves as a base for implementing transport protocols. /// Wrapper trait for async streams
/// Base transports can optionally be upgraded with TLS in order to support encryption. pub trait PtStream: AsyncRead + AsyncWrite + Unpin + Send {}
/// The implementation of our TLS authentication can be found in the
/// [`upgrade_tls`](TlsUpgrade) module.
pub trait Transport {
type Acceptor;
type Connector;
type Listener: Future<Output = Result<Self::Acceptor>>; #[cfg(feature = "p2p-transport-tcp")]
type Dial: Future<Output = Result<Self::Connector>>; impl PtStream for async_std::net::TcpStream {}
type TlsListener: Future<Output = Result<(TlsAcceptor, Self::Acceptor)>>; #[cfg(feature = "p2p-transport-tcp")]
type TlsDialer: Future<Output = Result<TlsStream<Self::Connector>>>; impl PtStream for async_rustls::TlsStream<async_std::net::TcpStream> {}
fn listen_on(self, url: Url) -> Result<Self::Listener> #[cfg(feature = "p2p-transport-tor")]
where impl PtStream for arti_client::DataStream {}
Self: Sized;
fn upgrade_listener(self, acceptor: Self::Acceptor) -> Result<Self::TlsListener> #[cfg(feature = "p2p-transport-tor")]
where impl PtStream for async_rustls::TlsStream<arti_client::DataStream> {}
Self: Sized;
fn dial(self, url: Url, timeout: Option<Duration>) -> Result<Self::Dial> /// Wrapper trait for async listeners
where #[async_trait]
Self: Sized; pub trait PtListener: Send + Sync + Unpin {
async fn next(&self) -> Result<(Box<dyn PtStream>, Url)>;
fn upgrade_dialer(self, stream: Self::Connector) -> Result<Self::TlsDialer>
where
Self: Sized;
} }

View File

@@ -16,114 +16,55 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{io, net::SocketAddr, pin::Pin, time::Duration}; use std::time::Duration;
use async_std::net::{TcpListener, TcpStream}; use rand::{rngs::OsRng, RngCore};
use fast_socks5::client::{Config, Socks5Stream};
use futures::prelude::*;
use futures_rustls::{TlsAcceptor, TlsStream};
use socket2::{Domain, Socket, TcpKeepalive, Type};
use url::Url; use url::Url;
use crate::{Error, Result}; use crate::{util::encoding::base32, Result};
use super::{TlsUpgrade, Transport}; /// Unique, randomly-generated per-connection ID that's used to
/// identify which connection a message belongs to.
#[derive(Clone, Eq, PartialEq, Hash)]
struct ConnectionId([u8; 32]);
#[derive(Clone)] impl ConnectionId {
pub struct NymTransport { fn generate() -> Self {
socks_url: Url, let mut bytes = [0u8; 32];
} OsRng.fill_bytes(&mut bytes);
Self(bytes)
impl NymTransport {
pub fn new() -> Result<Self> {
let socks_url = Url::parse("socks5://127.0.0.1:1080")?;
Ok(Self { socks_url })
} }
pub async fn do_dial(self, url: Url) -> Result<Socks5Stream<TcpStream>> { fn from_bytes(bytes: &[u8]) -> Self {
let socks_url_str = self.socks_url.socket_addrs(|| None)?[0].to_string(); let mut id = [0u8; 32];
let host = url.host().unwrap().to_string(); id[..].copy_from_slice(&bytes[0..32]);
let port = url.port().unwrap_or(80); ConnectionId(id)
let config = Config::default();
let stream = if !self.socks_url.username().is_empty() && self.socks_url.password().is_some()
{
Socks5Stream::connect_with_password(
socks_url_str,
host,
port,
self.socks_url.username().to_string(),
self.socks_url.password().unwrap().to_string(),
config,
)
.await?
} else {
Socks5Stream::connect(socks_url_str, host, port, config).await?
};
Ok(stream)
}
fn create_socket(&self, socket_addr: SocketAddr) -> io::Result<Socket> {
let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?;
if socket_addr.is_ipv6() {
socket.set_only_v6(true)?;
}
// TODO: Perhaps make these configurable
socket.set_nodelay(true)?;
let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(30));
socket.set_tcp_keepalive(&keepalive)?;
// TODO: Make sure to disallow running multiple instances of a program using this.
socket.set_reuse_port(true)?;
Ok(socket)
}
pub async fn do_listen(self, url: Url) -> Result<TcpListener> {
let socket_addr = url.socket_addrs(|| None)?[0];
let socket = self.create_socket(socket_addr)?;
socket.bind(&socket_addr.into())?;
socket.listen(1024)?;
socket.set_nonblocking(true)?;
Ok(TcpListener::from(std::net::TcpListener::from(socket)))
} }
} }
impl Transport for NymTransport { impl std::fmt::Debug for ConnectionId {
type Acceptor = TcpListener; fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Connector = Socks5Stream<TcpStream>; write!(f, "{}", base32::encode(false, &self.0).to_ascii_lowercase())
}
type Listener = Pin<Box<dyn Future<Output = Result<Self::Acceptor>> + Send>>; }
type Dial = Pin<Box<dyn Future<Output = Result<Self::Connector>> + Send>>;
/// Nym Dialer implementation
type TlsListener = Pin<Box<dyn Future<Output = Result<(TlsAcceptor, Self::Acceptor)>> + Send>>; #[derive(Debug, Clone)]
type TlsDialer = Pin<Box<dyn Future<Output = Result<TlsStream<Self::Connector>>> + Send>>; pub struct NymDialer;
fn listen_on(self, url: Url) -> Result<Self::Listener> { impl NymDialer {
match url.scheme() { /// Instantiate a new [`NymDialer`] object
"nym" | "nym+tls" => {} pub(crate) async fn new() -> Result<Self> {
x => return Err(Error::UnsupportedTransport(x.to_string())), Ok(Self {})
} }
Ok(Box::pin(self.do_listen(url)))
} pub(crate) async fn do_dial(
&self,
fn upgrade_listener(self, acceptor: Self::Acceptor) -> Result<Self::TlsListener> { endpoint: Url, // Recipient
let tlsupgrade = TlsUpgrade::new(); timeout: Option<Duration>,
Ok(Box::pin(tlsupgrade.upgrade_listener_tls(acceptor))) ) -> Result<()> {
} let id = ConnectionId::generate();
fn dial(self, url: Url, _timeout: Option<Duration>) -> Result<Self::Dial> { Ok(())
match url.scheme() {
"nym" | "nym+tls" => {}
x => return Err(Error::UnsupportedTransport(x.to_string())),
}
Ok(Box::pin(self.do_dial(url)))
}
fn upgrade_dialer(self, connector: Self::Connector) -> Result<Self::TlsDialer> {
let tlsupgrade = TlsUpgrade::new();
Ok(Box::pin(tlsupgrade.upgrade_dialer_tls(connector)))
} }
} }

View File

@@ -16,118 +16,32 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use async_std::net::{TcpListener, TcpStream}; use std::{io, time::Duration};
use std::{io, net::SocketAddr, pin::Pin, time::Duration};
use async_rustls::{TlsAcceptor, TlsStream};
use async_std::net::{SocketAddr, TcpListener as AsyncStdTcpListener, TcpStream};
use async_trait::async_trait; use async_trait::async_trait;
use futures::prelude::*;
use futures_rustls::{TlsAcceptor, TlsStream};
use log::{debug, error};
use socket2::{Domain, Socket, TcpKeepalive, Type}; use socket2::{Domain, Socket, TcpKeepalive, Type};
use url::Url; use url::Url;
use super::{socket_addr_to_url, TlsUpgrade, Transport, TransportListener, TransportStream}; use super::{PtListener, PtStream};
use crate::{Error, Result}; use crate::Result;
impl TransportStream for TcpStream {} /// TCP Dialer implementation
impl<T: TransportStream> TransportStream for TlsStream<T> {} #[derive(Debug, Clone)]
pub struct TcpDialer {
#[async_trait] /// TTL to set for opened sockets, or `None` for default.
impl TransportListener for TcpListener {
async fn next(&self) -> Result<(Box<dyn TransportStream>, Url)> {
let (stream, peer_addr) = match self.accept().await {
Ok((s, a)) => (s, a),
Err(err) => {
error!(target: "net::tcp", "Error listening for connections: {}", err);
return Err(Error::AcceptConnectionFailed(self.local_addr()?.to_string()))
}
};
let url = socket_addr_to_url(peer_addr, "tcp")?;
Ok((Box::new(stream), url))
}
}
#[async_trait]
impl TransportListener for (TlsAcceptor, TcpListener) {
async fn next(&self) -> Result<(Box<dyn TransportStream>, Url)> {
let (stream, peer_addr) = match self.1.accept().await {
Ok((s, a)) => (s, a),
Err(err) => {
error!(target: "net::tcp", "Error listening for connections: {}", err);
return Err(Error::AcceptConnectionFailed(self.1.local_addr()?.to_string()))
}
};
let stream = self.0.accept(stream).await;
let url = socket_addr_to_url(peer_addr, "tcp+tls")?;
if let Err(err) = stream {
error!(target: "net::tcp", "Error wrapping the connection {} with tls: {}", url, err);
return Err(Error::AcceptTlsConnectionFailed(self.1.local_addr()?.to_string()))
}
Ok((Box::new(TlsStream::Server(stream?)), url))
}
}
#[derive(Copy, Clone)]
pub struct TcpTransport {
/// TTL to set for opened sockets, or `None` for default
ttl: Option<u32>, ttl: Option<u32>,
/// Size of the listen backlog for listen sockets
backlog: i32,
} }
impl Transport for TcpTransport { impl TcpDialer {
type Acceptor = TcpListener; /// Instantiate a new [`TcpDialer`] with optional TTL.
type Connector = TcpStream; pub(crate) async fn new(ttl: Option<u32>) -> Result<Self> {
Ok(Self { ttl })
type Listener = Pin<Box<dyn Future<Output = Result<Self::Acceptor>> + Send>>;
type Dial = Pin<Box<dyn Future<Output = Result<Self::Connector>> + Send>>;
type TlsListener = Pin<Box<dyn Future<Output = Result<(TlsAcceptor, Self::Acceptor)>> + Send>>;
type TlsDialer = Pin<Box<dyn Future<Output = Result<TlsStream<Self::Connector>>> + Send>>;
fn listen_on(self, url: Url) -> Result<Self::Listener> {
match url.scheme() {
"tcp" | "tcp+tls" | "tls" => {}
x => return Err(Error::UnsupportedTransport(x.to_string())),
}
let socket_addr = url.socket_addrs(|| None)?[0];
debug!(target: "net::tcp", "{} transport: listening on {}", url.scheme(), socket_addr);
Ok(Box::pin(self.do_listen(socket_addr)))
} }
fn upgrade_listener(self, acceptor: Self::Acceptor) -> Result<Self::TlsListener> { /// Internal helper function to create a TCP socket.
let tlsupgrade = TlsUpgrade::new(); async fn create_socket(&self, socket_addr: SocketAddr) -> io::Result<Socket> {
Ok(Box::pin(tlsupgrade.upgrade_listener_tls(acceptor)))
}
fn dial(self, url: Url, timeout: Option<Duration>) -> Result<Self::Dial> {
match url.scheme() {
"tcp" | "tcp+tls" | "tls" => {}
x => return Err(Error::UnsupportedTransport(x.to_string())),
}
let socket_addr = url.socket_addrs(|| None)?[0];
debug!(target: "net::tcp", "{} transport: dialing {}", url.scheme(), socket_addr);
Ok(Box::pin(self.do_dial(socket_addr, timeout)))
}
fn upgrade_dialer(self, connector: Self::Connector) -> Result<Self::TlsDialer> {
let tlsupgrade = TlsUpgrade::new();
Ok(Box::pin(tlsupgrade.upgrade_dialer_tls(connector)))
}
}
impl TcpTransport {
pub fn new(ttl: Option<u32>, backlog: i32) -> Self {
Self { ttl, backlog }
}
fn create_socket(&self, socket_addr: SocketAddr) -> io::Result<Socket> {
let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 }; let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?; let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?;
@@ -139,30 +53,21 @@ impl TcpTransport {
socket.set_ttl(ttl)?; socket.set_ttl(ttl)?;
} }
// TODO: Perhaps make these configurable
socket.set_nodelay(true)?; socket.set_nodelay(true)?;
let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(20)); let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(20));
socket.set_tcp_keepalive(&keepalive)?; socket.set_tcp_keepalive(&keepalive)?;
// TODO: Make sure to disallow running multiple instances of a program using this.
socket.set_reuse_port(true)?; socket.set_reuse_port(true)?;
Ok(socket) Ok(socket)
} }
async fn do_listen(self, socket_addr: SocketAddr) -> Result<TcpListener> { /// Internal dial function
let socket = self.create_socket(socket_addr)?; pub(crate) async fn do_dial(
socket.bind(&socket_addr.into())?; &self,
socket.listen(self.backlog)?;
socket.set_nonblocking(true)?;
Ok(TcpListener::from(std::net::TcpListener::from(socket)))
}
async fn do_dial(
self,
socket_addr: SocketAddr, socket_addr: SocketAddr,
timeout: Option<Duration>, timeout: Option<Duration>,
) -> Result<TcpStream> { ) -> Result<TcpStream> {
let socket = self.create_socket(socket_addr)?; let socket = self.create_socket(socket_addr).await?;
let connection = if timeout.is_some() { let connection = if timeout.is_some() {
socket.connect_timeout(&socket_addr.into(), timeout.unwrap()) socket.connect_timeout(&socket_addr.into(), timeout.unwrap())
@@ -175,10 +80,83 @@ impl TcpTransport {
Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {} Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {}
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; }
socket.set_nonblocking(true)?; socket.set_nonblocking(true)?;
let stream = TcpStream::from(std::net::TcpStream::from(socket)); let stream = TcpStream::from(std::net::TcpStream::from(socket));
Ok(stream) Ok(stream)
} }
} }
/// TCP Listener implementation
#[derive(Debug, Clone)]
pub struct TcpListener {
/// Size of the listen backlog for listen sockets
backlog: i32,
}
impl TcpListener {
/// Instantiate a new [`TcpListener`] with given backlog size.
pub async fn new(backlog: i32) -> Result<Self> {
Ok(Self { backlog })
}
/// Internal helper function to create a TCP socket.
async fn create_socket(&self, socket_addr: SocketAddr) -> io::Result<Socket> {
let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?;
if socket_addr.is_ipv6() {
socket.set_only_v6(true)?;
}
socket.set_nodelay(true)?;
let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(20));
socket.set_tcp_keepalive(&keepalive)?;
socket.set_reuse_port(true)?;
Ok(socket)
}
/// Internal listen function
pub(crate) async fn do_listen(&self, socket_addr: SocketAddr) -> Result<AsyncStdTcpListener> {
let socket = self.create_socket(socket_addr).await?;
socket.bind(&socket_addr.into())?;
socket.listen(self.backlog)?;
socket.set_nonblocking(true)?;
Ok(AsyncStdTcpListener::from(std::net::TcpListener::from(socket)))
}
}
#[async_trait]
impl PtListener for AsyncStdTcpListener {
async fn next(&self) -> Result<(Box<dyn PtStream>, Url)> {
let (stream, peer_addr) = match self.accept().await {
Ok((s, a)) => (s, a),
Err(e) => return Err(e.into()),
};
let url = Url::parse(&format!("tcp://{}", peer_addr))?;
Ok((Box::new(stream), url))
}
}
#[async_trait]
impl PtListener for (TlsAcceptor, AsyncStdTcpListener) {
async fn next(&self) -> Result<(Box<dyn PtStream>, Url)> {
let (stream, peer_addr) = match self.1.accept().await {
Ok((s, a)) => (s, a),
Err(e) => return Err(e.into()),
};
let stream = self.0.accept(stream).await;
let url = Url::parse(&format!("tcp+tls://{}", peer_addr))?;
if let Err(e) = stream {
return Err(e.into())
}
Ok((Box::new(TlsStream::Server(stream.unwrap())), url))
}
}

View File

@@ -18,9 +18,7 @@
use std::time::SystemTime; use std::time::SystemTime;
use async_std::{net::TcpListener, sync::Arc}; use async_rustls::{
use futures::prelude::*;
use futures_rustls::{
rustls, rustls,
rustls::{ rustls::{
client::{ServerCertVerified, ServerCertVerifier}, client::{ServerCertVerified, ServerCertVerifier},
@@ -31,6 +29,7 @@ use futures_rustls::{
}, },
TlsAcceptor, TlsConnector, TlsStream, TlsAcceptor, TlsConnector, TlsStream,
}; };
use async_std::sync::Arc;
use log::error; use log::error;
use rustls_pemfile::pkcs8_private_keys; use rustls_pemfile::pkcs8_private_keys;
use x509_parser::{ use x509_parser::{
@@ -201,14 +200,19 @@ impl TlsUpgrade {
let secret_key = pkcs8_private_keys(&mut keypair_pem.as_bytes()).unwrap(); let secret_key = pkcs8_private_keys(&mut keypair_pem.as_bytes()).unwrap();
let secret_key = rustls::PrivateKey(secret_key[0].clone()); let secret_key = rustls::PrivateKey(secret_key[0].clone());
let altname = base32::encode(false, keypair.pk.as_slice()).to_ascii_lowercase(); let altnames = vec![base32::encode(false, keypair.pk.as_slice())];
let altnames = vec![altname];
let mut cert_params = rcgen::CertificateParams::new(altnames); let mut cert_params = rcgen::CertificateParams::new(altnames);
cert_params.alg = &rcgen::PKCS_ED25519; cert_params.alg = &rcgen::PKCS_ED25519;
cert_params.key_pair = Some(rcgen::KeyPair::from_pem(&keypair_pem).unwrap()); cert_params.key_pair = Some(rcgen::KeyPair::from_pem(&keypair_pem).unwrap());
cert_params.extended_key_usages = vec![
rcgen::ExtendedKeyUsagePurpose::ClientAuth,
rcgen::ExtendedKeyUsagePurpose::ServerAuth,
];
let certificate = rcgen::Certificate::from_params(cert_params).unwrap(); let certificate = rcgen::Certificate::from_params(cert_params).unwrap();
let certificate = rustls::Certificate(certificate.serialize_der().unwrap()); let certificate = certificate.serialize_der().unwrap();
let certificate = rustls::Certificate(certificate);
let client_cert_verifier = Arc::new(ClientCertificateVerifier {}); let client_cert_verifier = Arc::new(ClientCertificateVerifier {});
let server_config = Arc::new( let server_config = Arc::new(
@@ -237,22 +241,24 @@ impl TlsUpgrade {
Self { server_config, client_config } Self { server_config, client_config }
} }
pub async fn upgrade_listener_tls(
self,
listener: TcpListener,
) -> Result<(TlsAcceptor, TcpListener)> {
Ok((TlsAcceptor::from(self.server_config), listener))
}
pub async fn upgrade_dialer_tls<IO>(self, stream: IO) -> Result<TlsStream<IO>> pub async fn upgrade_dialer_tls<IO>(self, stream: IO) -> Result<TlsStream<IO>>
where where
IO: AsyncRead + AsyncWrite + Unpin, IO: super::PtStream,
{ {
let server_name = ServerName::try_from("dark.fi").unwrap(); let server_name = ServerName::try_from("dark.fi").unwrap();
let connector = TlsConnector::from(self.client_config); let connector = TlsConnector::from(self.client_config);
let stream = connector.connect(server_name, stream).await?; let stream = connector.connect(server_name, stream).await?;
Ok(TlsStream::Client(stream)) Ok(TlsStream::Client(stream))
} }
// FIXME: Try to find a transparent way for this instead of implementing separately for all
#[cfg(feature = "p2p-transport-tcp")]
pub async fn upgrade_listener_tcp_tls(
self,
listener: async_std::net::TcpListener,
) -> Result<(TlsAcceptor, async_std::net::TcpListener)> {
Ok((TlsAcceptor::from(self.server_config), listener))
}
} }
impl Default for TlsUpgrade { impl Default for TlsUpgrade {

View File

@@ -16,307 +16,39 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{ use std::time::Duration;
io,
io::{BufRead, BufReader, Write},
net::SocketAddr,
pin::Pin,
time::Duration,
};
use async_std::{ use arti_client::{BootstrapBehavior, DataStream, TorClient};
net::{TcpListener, TcpStream}, use async_std::future;
sync::Arc,
};
use fast_socks5::client::{Config, Socks5Stream};
use futures::prelude::*;
use futures_rustls::{TlsAcceptor, TlsStream};
use socket2::{Domain, Socket, TcpKeepalive, Type};
use url::Url;
use crate::{Error, Result}; use crate::Result;
use super::{TlsUpgrade, Transport, TransportStream}; /// Tor Dialer implementation
#[derive(Debug, Clone)]
pub struct TorDialer;
/// Implements communication through the tor proxy service. impl TorDialer {
/// /// Instantiate a new [`TorDialer`] object
/// ## Dialing pub(crate) async fn new() -> Result<Self> {
/// Ok(Self {})
/// The tor service must be running for dialing to work. Url of it has to be passed to the
/// constructor.
///
/// ## Listening
///
/// Two ways of setting up hidden services are allowed: hidden services manually set up by the user
/// in the torc file or ephemereal hidden services created and deleted on the fly. For the latter,
/// the user must set up the tor control port[^controlport].
///
/// Having manually configured services forces the program to use pre-defined ports, i.e. it has no
/// way of changing them.
///
/// Before calling [listen_on][transportlisten] on a local address, make sure that either a hidden
/// service pointing to that address was configured or that [create_ehs][torcreateehs] was called
/// with this address.
///
/// [^controlport] [Open control port](https://wiki.archlinux.org/title/tor#Open_Tor_ControlPort)
///
/// ### Warning on cloning
/// Cloning this structure increments the reference count to the already open
/// socket, which means ephemereal hidden services opened with the cloned instance will live as
/// long as there are clones. For this reason, I'd clone it only when you are sure you want this
/// behaviour. Don't be lazy!
///
/// [transportlisten]: Transport
/// [torcreateehs]: TorTransport::create_ehs
#[derive(Clone)]
pub struct TorTransport {
socks_url: Url,
tor_controller: Option<TorController>,
}
/// Represents information needed to communicate with the Tor control socket
#[derive(Clone)]
struct TorController {
socket: Arc<Socket>, // Need to hold this socket open as long as the tor trasport is alive, so ephemeral services are dropped when TorTransport is dropped
auth: String,
}
/// Contains the configuration to communicate with the Tor Controler
///
/// When cloned, the socket is not reopened since we use reference count.
/// The hidden services created live as long as clones of the struct.
impl TorController {
/// Creates a new TorTransport
///
/// # Arguments
///
/// * `url` - url to connect to the tor control. For example tcp://127.0.0.1:9051
///
/// * `auth` - either authentication cookie bytes (32 bytes) as hex in a string
/// or a password as a quoted string.
///
/// Cookie string: `assert_eq!(auth,"886b9177aec471965abd34b6a846dc32cf617dcff0625cba7a414e31dd4b75a0")`
///
/// Password string: `assert_eq!(auth,"\"mypassword\"")`
pub fn new(url: Url, auth: String) -> Result<Self> {
let socket_addr = url.socket_addrs(|| None)?[0];
let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?;
if socket_addr.is_ipv6() {
socket.set_only_v6(true)?;
}
match socket.connect(&socket_addr.into()) {
Ok(()) => {}
Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {}
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
Err(err) => return Err(err.into()),
};
Ok(Self { socket: Arc::new(socket), auth })
} }
/// Creates an ephemeral hidden service pointing to local address, returns onion address
///
/// # Arguments
///
/// * `url` - url that the hidden service maps to.
pub fn create_ehs(&self, url: Url) -> Result<Url> {
let local_socket = self.socket.try_clone()?;
let mut stream = std::net::TcpStream::from(local_socket);
stream.set_write_timeout(Some(Duration::from_secs(2)))?; /// Internal dial function
let host = url.host().unwrap(); pub(crate) async fn do_dial(
let port = url.port().unwrap(); &self,
host: &str,
port: u16,
timeout: Option<Duration>,
) -> Result<DataStream> {
let client = TorClient::builder()
.bootstrap_behavior(BootstrapBehavior::OnDemand)
.create_unbootstrapped()?;
let payload = format!( if timeout.is_some() {
"AUTHENTICATE {a}\r\nADD_ONION NEW:ED25519-V3 Flags=DiscardPK Port={p},{h}:{p}\r\n", let res = future::timeout(timeout.unwrap(), client.connect((host, port))).await?;
a = self.auth, return Ok(res?)
p = port,
h = host
);
stream.write_all(payload.as_bytes())?;
// 1s is maybe a bit too much. Gives tor time to reply
stream.set_read_timeout(Some(Duration::from_secs(1)))?;
let mut reader = BufReader::new(stream);
let mut repl = String::new();
while let Ok(nbytes) = reader.read_line(&mut repl) {
if nbytes == 0 {
break
}
} }
let spl: Vec<&str> = repl.split('\n').collect(); Ok(client.connect((host, port)).await?)
if spl.len() != 4 {
return Err(Error::TorError(format!("Unsuccessful reply from TorControl: {:?}", spl)))
}
let onion: Vec<&str> = spl[1].split('=').collect();
if onion.len() != 2 {
return Err(Error::TorError(format!("Unsuccessful reply from TorControl: {:?}", spl)))
}
let onion = &onion[1][..onion[1].len() - 1];
let hurl = format!("tcp://{}.onion:{}", onion, port);
Ok(Url::parse(&hurl)?)
}
}
impl TorTransport {
/// Creates a new TorTransport
///
/// # Arguments
///
/// * `socks_url` - url to connect to the tor service. For example socks5://127.0.0.1:9050
///
/// * `control_info` - Possibility to open a control connection to create ephemeral hidden
/// services that live as long as the TorTransport.
/// It is a tuple of the control socket url and authentication cookie as string
/// represented in hex.
pub fn new(socks_url: Url, control_info: Option<(Url, String)>) -> Result<Self> {
match control_info {
Some(info) => {
let (url, auth) = info;
let tor_controller = Some(TorController::new(url, auth)?);
Ok(Self { socks_url, tor_controller })
}
None => Ok(Self { socks_url, tor_controller: None }),
}
}
/// Query the environment for listener Tor variables, or fallback to defaults
pub fn get_listener_env() -> Result<(Url, Url, String)> {
let socks5_url = Url::parse(
&std::env::var("DARKFI_TOR_SOCKS5_URL")
.unwrap_or_else(|_| "socks5://127.0.0.1:9050".to_string()),
)?;
let torc_url = Url::parse(
&std::env::var("DARKFI_TOR_CONTROL_URL")
.unwrap_or_else(|_| "tcp://127.0.0.1:9051".to_string()),
)?;
let auth_cookie = std::env::var("DARKFI_TOR_COOKIE");
if auth_cookie.is_err() {
return Err(Error::TorError(
"Please set the env var DARKFI_TOR_COOKIE to the configured Tor cookie file.\n\
For example:\n\
export DARKFI_TOR_COOKIE='/var/lib/tor/control_auth_cookie'"
.to_string(),
))
}
Ok((socks5_url, torc_url, auth_cookie.unwrap()))
}
/// Query the environment for the dialer Tor variables, or fallback to defaults
pub fn get_dialer_env() -> Result<Url> {
Ok(Url::parse(
&std::env::var("DARKFI_TOR_SOCKS5_URL")
.unwrap_or_else(|_| "socks5://127.0.0.1:9050".to_string()),
)?)
}
/// Creates an ephemeral hidden service pointing to local address, returns onion address
/// when successful.
///
/// # Arguments
///
/// * `url` - url that the hidden service maps to.
pub fn create_ehs(&self, url: Url) -> Result<Url> {
let tor_controller = self.tor_controller.as_ref();
if tor_controller.is_none() {
return Err(Error::TorError("No controller configured for this transport".to_string()))
};
tor_controller.unwrap().create_ehs(url)
}
pub async fn do_dial(self, url: Url) -> Result<Socks5Stream<TcpStream>> {
let socks_url_str = self.socks_url.socket_addrs(|| None)?[0].to_string();
let host = url.host().unwrap().to_string();
let port = url.port().unwrap_or(80);
let config = Config::default();
let stream = if !self.socks_url.username().is_empty() && self.socks_url.password().is_some()
{
Socks5Stream::connect_with_password(
socks_url_str,
host,
port,
self.socks_url.username().to_string(),
self.socks_url.password().unwrap().to_string(),
config,
)
.await?
} else {
Socks5Stream::connect(socks_url_str, host, port, config).await?
};
Ok(stream)
}
fn create_socket(&self, socket_addr: SocketAddr) -> io::Result<Socket> {
let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 };
let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?;
if socket_addr.is_ipv6() {
socket.set_only_v6(true)?;
}
// TODO: Perhaps make these configurable
socket.set_nodelay(true)?;
let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(30));
socket.set_tcp_keepalive(&keepalive)?;
// TODO: Make sure to disallow running multiple instances of a program using this.
socket.set_reuse_port(true)?;
Ok(socket)
}
pub async fn do_listen(self, url: Url) -> Result<TcpListener> {
let socket_addr = url.socket_addrs(|| None)?[0];
let socket = self.create_socket(socket_addr)?;
socket.bind(&socket_addr.into())?;
socket.listen(1024)?;
socket.set_nonblocking(true)?;
Ok(TcpListener::from(std::net::TcpListener::from(socket)))
}
}
impl<T: TransportStream> TransportStream for Socks5Stream<T> {}
impl Transport for TorTransport {
type Acceptor = TcpListener;
type Connector = Socks5Stream<TcpStream>;
type Listener = Pin<Box<dyn Future<Output = Result<Self::Acceptor>> + Send>>;
type Dial = Pin<Box<dyn Future<Output = Result<Self::Connector>> + Send>>;
type TlsListener = Pin<Box<dyn Future<Output = Result<(TlsAcceptor, Self::Acceptor)>> + Send>>;
type TlsDialer = Pin<Box<dyn Future<Output = Result<TlsStream<Self::Connector>>> + Send>>;
fn listen_on(self, url: Url) -> Result<Self::Listener> {
match url.scheme() {
"tor" | "tor+tls" => {}
x => return Err(Error::UnsupportedTransport(x.to_string())),
}
Ok(Box::pin(self.do_listen(url)))
}
fn upgrade_listener(self, acceptor: Self::Acceptor) -> Result<Self::TlsListener> {
let tlsupgrade = TlsUpgrade::new();
Ok(Box::pin(tlsupgrade.upgrade_listener_tls(acceptor)))
}
fn dial(self, url: Url, _timeout: Option<Duration>) -> Result<Self::Dial> {
match url.scheme() {
"tor" | "tor+tls" => {}
"tcp" | "tcp+tls" | "tls" => {}
x => return Err(Error::UnsupportedTransport(x.to_string())),
}
Ok(Box::pin(self.do_dial(url)))
}
fn upgrade_dialer(self, connector: Self::Connector) -> Result<Self::TlsDialer> {
let tlsupgrade = TlsUpgrade::new();
Ok(Box::pin(tlsupgrade.upgrade_dialer_tls(connector)))
} }
} }

View File

@@ -1,142 +0,0 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{os::unix::net::SocketAddr, pin::Pin, time::Duration};
use async_std::os::unix::net::{UnixListener, UnixStream};
use async_trait::async_trait;
use futures::prelude::*;
use futures_rustls::{TlsAcceptor, TlsStream};
use log::{debug, error};
use url::Url;
use super::{Transport, TransportListener, TransportStream};
use crate::{Error, Result};
fn unix_socket_addr_to_string(addr: std::os::unix::net::SocketAddr) -> String {
addr.as_pathname()
.unwrap_or(&std::path::PathBuf::from("unix:///"))
.to_str()
.unwrap_or("unix:///")
.into()
}
impl TransportStream for UnixStream {}
#[async_trait]
impl TransportListener for UnixListener {
async fn next(&self) -> Result<(Box<dyn TransportStream>, Url)> {
let (stream, peer_addr) = match self.accept().await {
Ok((s, a)) => (s, a),
Err(err) => {
error!(target: "net::unix", "Error listening for connections: {}", err);
return Err(Error::AcceptConnectionFailed(unix_socket_addr_to_string(
self.local_addr()?,
)))
}
};
let url = Url::parse(&unix_socket_addr_to_string(peer_addr))?;
Ok((Box::new(stream), url))
}
}
#[async_trait]
impl TransportListener for (TlsAcceptor, UnixListener) {
async fn next(&self) -> Result<(Box<dyn TransportStream>, Url)> {
unimplemented!("TLS not supported for Unix sockets");
}
}
#[derive(Copy, Clone)]
pub struct UnixTransport {}
impl Transport for UnixTransport {
type Acceptor = UnixListener;
type Connector = UnixStream;
type Listener = Pin<Box<dyn Future<Output = Result<Self::Acceptor>> + Send>>;
type Dial = Pin<Box<dyn Future<Output = Result<Self::Connector>> + Send>>;
type TlsListener = Pin<Box<dyn Future<Output = Result<(TlsAcceptor, Self::Acceptor)>> + Send>>;
type TlsDialer = Pin<Box<dyn Future<Output = Result<TlsStream<Self::Connector>>> + Send>>;
fn listen_on(self, url: Url) -> Result<Self::Listener> {
match url.scheme() {
"unix" => {}
x => return Err(Error::UnsupportedTransport(x.to_string())),
}
let socket_path = url.path();
let socket_addr = SocketAddr::from_pathname(socket_path)?;
debug!(target: "net::unix", "{} transport: listening on {}", url.scheme(), socket_path);
Ok(Box::pin(self.do_listen(socket_addr)))
}
fn upgrade_listener(self, _acceptor: Self::Acceptor) -> Result<Self::TlsListener> {
unimplemented!("TLS not supported for Unix sockets");
}
fn dial(self, url: Url, timeout: Option<Duration>) -> Result<Self::Dial> {
match url.scheme() {
"unix" => {}
x => return Err(Error::UnsupportedTransport(x.to_string())),
}
let socket_path = url.path();
let socket_addr = SocketAddr::from_pathname(socket_path)?;
debug!(target: "net::unix", "{} transport: dialing {}", url.scheme(), socket_path);
Ok(Box::pin(self.do_dial(socket_addr, timeout)))
}
fn upgrade_dialer(self, _connector: Self::Connector) -> Result<Self::TlsDialer> {
unimplemented!("TLS not supported for Unix sockets");
}
}
impl Default for UnixTransport {
fn default() -> Self {
Self::new()
}
}
impl UnixTransport {
pub fn new() -> Self {
Self {}
}
async fn do_listen(self, socket_addr: SocketAddr) -> Result<UnixListener> {
// We're a bit rough here and delete the socket.
let socket_path = socket_addr.as_pathname().unwrap();
if std::fs::metadata(socket_path).is_ok() {
std::fs::remove_file(socket_path)?;
}
let socket = UnixListener::bind(socket_path).await?;
Ok(socket)
}
async fn do_dial(
self,
socket_addr: SocketAddr,
_timeout: Option<Duration>,
) -> Result<UnixStream> {
let socket_path = socket_addr.as_pathname().unwrap();
let stream = UnixStream::connect(&socket_path).await?;
Ok(stream)
}
}

View File

@@ -27,9 +27,7 @@ use url::Url;
use super::jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult}; use super::jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResult};
use crate::{ use crate::{
net::transport::{ net::transport::{Dialer, PtStream},
TcpTransport, TorTransport, Transport, TransportName, TransportStream, UnixTransport,
},
system::SubscriberPtr, system::SubscriberPtr,
Error, Result, Error, Result,
}; };
@@ -171,63 +169,17 @@ impl RpcClient {
let (result_send, result_recv) = smol::channel::unbounded(); let (result_send, result_recv) = smol::channel::unbounded();
let (stop_send, stop_recv) = smol::channel::unbounded(); let (stop_send, stop_recv) = smol::channel::unbounded();
let transport_name = TransportName::try_from(uri.clone())?; let dialer = Dialer::new(uri.clone()).await?;
// TODO: Revisit the timeout here
macro_rules! reqrep { let stream = dialer.dial(None).await?;
($stream:expr, $transport:expr, $upgrade:expr) => {{ smol::spawn(Self::reqrep_loop(stream, result_send, data_recv, stop_recv)).detach();
if let Err(err) = $stream {
error!(target: "rpc::client", "JSON-RPC client setup for {} failed: {}", uri, err);
return Err(Error::ConnectFailed)
}
let stream = $stream?.await;
if let Err(err) = stream {
error!(target: "rpc::client", "JSON-RPC client connection to {} failed: {}", uri, err);
return Err(Error::ConnectFailed)
}
let stream = stream?;
match $upgrade {
None => {
smol::spawn(Self::reqrep_loop(stream, result_send, data_recv, stop_recv))
.detach();
}
Some(u) if u == "tls" => {
let stream = $transport.upgrade_dialer(stream)?.await?;
smol::spawn(Self::reqrep_loop(stream, result_send, data_recv, stop_recv))
.detach();
}
Some(u) => return Err(Error::UnsupportedTransportUpgrade(u)),
}
}};
}
match transport_name {
TransportName::Tcp(upgrade) => {
let transport = TcpTransport::new(None, 1024);
let stream = transport.dial(uri.clone(), None);
reqrep!(stream, transport, upgrade);
}
TransportName::Tor(upgrade) => {
let socks5_url = TorTransport::get_dialer_env()?;
let transport = TorTransport::new(socks5_url, None)?;
let stream = transport.clone().dial(uri.clone(), None);
reqrep!(stream, transport, upgrade);
}
TransportName::Unix => {
let transport = UnixTransport::new();
let stream = transport.dial(uri.clone(), None);
reqrep!(stream, transport, None);
}
_ => unimplemented!(),
}
Ok((data_send, result_recv, stop_send)) Ok((data_send, result_recv, stop_send))
} }
/// Internal function that loops on a given stream and multiplexes the data. /// Internal function that loops on a given stream and multiplexes the data.
async fn reqrep_loop<T: TransportStream>( async fn reqrep_loop(
mut stream: T, mut stream: Box<dyn PtStream>,
result_send: smol::channel::Sender<JsonResult>, result_send: smol::channel::Sender<JsonResult>,
data_recv: smol::channel::Receiver<(Value, bool)>, data_recv: smol::channel::Receiver<(Value, bool)>,
stop_recv: smol::channel::Receiver<()>, stop_recv: smol::channel::Receiver<()>,

View File

@@ -25,11 +25,8 @@ use url::Url;
use super::jsonrpc::{JsonRequest, JsonResult}; use super::jsonrpc::{JsonRequest, JsonResult};
use crate::{ use crate::{
net::transport::{ net::transport::{Listener, PtListener, PtStream},
TcpTransport, TorTransport, Transport, TransportListener, TransportName, TransportStream, Result,
UnixTransport,
},
Error, Result,
}; };
/// Asynchronous trait implementing a handler for incoming JSON-RPC requests. /// Asynchronous trait implementing a handler for incoming JSON-RPC requests.
@@ -43,7 +40,7 @@ pub trait RequestHandler: Sync + Send {
/// Internal accept function that runs inside a loop for accepting incoming /// Internal accept function that runs inside a loop for accepting incoming
/// JSON-RPC requests and passing them to the [`RequestHandler`]. /// JSON-RPC requests and passing them to the [`RequestHandler`].
async fn accept( async fn accept(
mut stream: Box<dyn TransportStream>, mut stream: Box<dyn PtStream>,
peer_addr: Url, peer_addr: Url,
rh: Arc<impl RequestHandler + 'static>, rh: Arc<impl RequestHandler + 'static>,
) -> Result<()> { ) -> Result<()> {
@@ -115,7 +112,7 @@ async fn accept(
/// Wrapper function around [`accept()`] to take the incoming connection and /// Wrapper function around [`accept()`] to take the incoming connection and
/// pass it forward. /// pass it forward.
async fn run_accept_loop( async fn run_accept_loop(
listener: Box<dyn TransportListener>, listener: Box<dyn PtListener>,
rh: Arc<impl RequestHandler + 'static>, rh: Arc<impl RequestHandler + 'static>,
ex: Arc<smol::Executor<'_>>, ex: Arc<smol::Executor<'_>>,
) -> Result<()> { ) -> Result<()> {
@@ -142,63 +139,8 @@ pub async fn listen_and_serve(
) -> Result<()> { ) -> Result<()> {
debug!(target: "rpc::server", "Trying to bind listener on {}", accept_url); debug!(target: "rpc::server", "Trying to bind listener on {}", accept_url);
macro_rules! accept { let listener = Listener::new(accept_url).await?.listen().await?;
($listener:expr, $transport:expr, $upgrade:expr) => {{ run_accept_loop(listener, rh, ex.clone()).await?;
if let Err(err) = $listener {
error!(target: "rpc::server", "JSON-RPC server setup for {} failed: {}", accept_url, err);
return Err(Error::BindFailed(accept_url.as_str().into()))
}
let listener = $listener?.await;
if let Err(err) = listener {
error!(target: "rpc::server", "JSON-RPC listener bind to {} failed: {}", accept_url, err);
return Err(Error::BindFailed(accept_url.as_str().into()))
}
let listener = listener?;
match $upgrade {
None => {
info!(target: "rpc::server", "JSON-RPC listener bound to {}", accept_url);
run_accept_loop(Box::new(listener), rh, ex.clone()).await?;
}
Some(u) if u == "tls" => {
let tls_listener = $transport.upgrade_listener(listener)?.await?;
info!(target: "rpc::server", "JSON-RPC listener bound to {}", accept_url);
run_accept_loop(Box::new(tls_listener), rh, ex.clone()).await?;
}
Some(u) => return Err(Error::UnsupportedTransportUpgrade(u)),
}
}};
}
let transport_name = TransportName::try_from(accept_url.clone())?;
match transport_name {
TransportName::Tcp(upgrade) => {
let transport = TcpTransport::new(None, 1024);
let listener = transport.listen_on(accept_url.clone());
accept!(listener, transport, upgrade);
}
TransportName::Tor(upgrade) => {
let (socks5_url, torc_url, auth_cookie) = TorTransport::get_listener_env()?;
let auth_cookie = hex::encode(std::fs::read(auth_cookie).unwrap());
let transport = TorTransport::new(socks5_url, Some((torc_url, auth_cookie)))?;
// Generate EHS pointing to local address
let hurl = transport.create_ehs(accept_url.clone())?;
info!(target: "rpc::server", "Created ephemeral hidden service: {}", hurl.to_string());
let listener = transport.clone().listen_on(accept_url.clone());
accept!(listener, transport, upgrade);
}
TransportName::Unix => {
let transport = UnixTransport::new();
let listener = transport.listen_on(accept_url.clone());
accept!(listener, transport, None);
}
_ => unimplemented!(),
}
Ok(()) Ok(())
} }

View File

@@ -23,6 +23,7 @@ use smol::Executor;
pub type StoppableTaskPtr = Arc<StoppableTask>; pub type StoppableTaskPtr = Arc<StoppableTask>;
#[derive(Debug)]
pub struct StoppableTask { pub struct StoppableTask {
stop_send: smol::channel::Sender<()>, stop_send: smol::channel::Sender<()>,
stop_recv: smol::channel::Receiver<()>, stop_recv: smol::channel::Receiver<()>,

View File

@@ -26,6 +26,7 @@ pub type SubscriberPtr<T> = Arc<Subscriber<T>>;
pub type SubscriptionId = u64; pub type SubscriptionId = u64;
#[derive(Debug)]
pub struct Subscription<T> { pub struct Subscription<T> {
id: SubscriptionId, id: SubscriptionId,
recv_queue: smol::channel::Receiver<T>, recv_queue: smol::channel::Receiver<T>,
@@ -54,7 +55,8 @@ impl<T: Clone> Subscription<T> {
} }
} }
// Simple broadcast (publish-subscribe) class /// Simple broadcast (publish-subscribe) class
#[derive(Debug)]
pub struct Subscriber<T> { pub struct Subscriber<T> {
subs: Mutex<HashMap<u64, smol::channel::Sender<T>>>, subs: Mutex<HashMap<u64, smol::channel::Sender<T>>>,
} }

View File

@@ -16,66 +16,31 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::{env::var, fs};
use async_std::{ use async_std::{
io, io,
io::{ReadExt, WriteExt}, io::{ReadExt, WriteExt},
stream::StreamExt,
task, task,
}; };
use url::Url; use url::Url;
use darkfi::net::transport::{NymTransport, TcpTransport, TorTransport, Transport, UnixTransport}; use darkfi::net::transport::{Dialer, Listener};
#[async_std::test]
async fn unix_transport() {
let unix = UnixTransport::new();
let url = Url::parse("unix:///tmp/darkfi_test.sock").unwrap();
let listener = unix.listen_on(url.clone()).unwrap().await.unwrap();
task::spawn(async move {
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await {
let stream = stream.unwrap();
let (reader, writer) = &mut (&stream, &stream);
io::copy(reader, writer).await.unwrap();
}
});
let payload = b"ohai unix";
let mut client = unix.dial(url, None).unwrap().await.unwrap();
client.write_all(payload).await.unwrap();
let mut buf = vec![0_u8; 9];
client.read_exact(&mut buf).await.unwrap();
std::fs::remove_file("/tmp/darkfi_test.sock").unwrap();
assert_eq!(buf, payload);
}
#[async_std::test] #[async_std::test]
async fn tcp_transport() { async fn tcp_transport() {
let tcp = TcpTransport::new(None, 1024);
let url = Url::parse("tcp://127.0.0.1:5432").unwrap(); let url = Url::parse("tcp://127.0.0.1:5432").unwrap();
let listener = Listener::new(url.clone()).await.unwrap().listen().await.unwrap();
let listener = tcp.listen_on(url.clone()).unwrap().await.unwrap();
task::spawn(async move { task::spawn(async move {
let mut incoming = listener.incoming(); let (stream, _) = listener.next().await.unwrap();
while let Some(stream) = incoming.next().await { let (mut reader, mut writer) = smol::io::split(stream);
let stream = stream.unwrap(); io::copy(&mut reader, &mut writer).await.unwrap();
let (reader, writer) = &mut (&stream, &stream);
io::copy(reader, writer).await.unwrap();
}
}); });
let payload = b"ohai tcp"; let payload = b"ohai tcp";
let mut client = tcp.dial(url, None).unwrap().await.unwrap(); let dialer = Dialer::new(url).await.unwrap();
let mut client = dialer.dial(None).await.unwrap();
client.write_all(payload).await.unwrap(); client.write_all(payload).await.unwrap();
let mut buf = vec![0_u8; 8]; let mut buf = vec![0u8; 8];
client.read_exact(&mut buf).await.unwrap(); client.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, payload); assert_eq!(buf, payload);
@@ -83,193 +48,20 @@ async fn tcp_transport() {
#[async_std::test] #[async_std::test]
async fn tcp_tls_transport() { async fn tcp_tls_transport() {
let tcp = TcpTransport::new(None, 1024);
let url = Url::parse("tcp+tls://127.0.0.1:5433").unwrap(); let url = Url::parse("tcp+tls://127.0.0.1:5433").unwrap();
let listener = Listener::new(url.clone()).await.unwrap().listen().await.unwrap();
let listener = tcp.listen_on(url.clone()).unwrap().await.unwrap();
let (acceptor, listener) = tcp.upgrade_listener(listener).unwrap().await.unwrap();
task::spawn(async move { task::spawn(async move {
let mut incoming = listener.incoming(); let (stream, _) = listener.next().await.unwrap();
while let Some(stream) = incoming.next().await { let (mut reader, mut writer) = smol::io::split(stream);
let stream = stream.unwrap(); io::copy(&mut reader, &mut writer).await.unwrap();
let stream = acceptor.accept(stream).await.unwrap();
let (mut reader, mut writer) = smol::io::split(stream);
match io::copy(&mut reader, &mut writer).await {
Ok(_) => {}
Err(e) => {
if e.kind() != std::io::ErrorKind::UnexpectedEof {
panic!("{}", e);
}
}
}
}
}); });
let payload = b"ohai tls"; let payload = b"ohai tls";
let client = tcp.dial(url, None).unwrap().await.unwrap(); let dialer = Dialer::new(url).await.unwrap();
let mut client = tcp.upgrade_dialer(client).unwrap().await.unwrap(); let mut client = dialer.dial(None).await.unwrap();
client.write_all(payload).await.unwrap(); client.write_all(payload).await.unwrap();
let mut buf = vec![0_u8; 8]; let mut buf = vec![0u8; 8];
client.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, payload);
}
#[async_std::test]
#[ignore]
async fn tor_transport_no_control() {
let url = Url::parse("socks5://127.0.0.1:9050").unwrap();
let hurl = var("DARKFI_TOR_LOCAL_ADDRESS")
.expect("Please set the env var DARKFI_TOR_LOCAL_ADDRESS to the configured local address in hidden service. \
For example: \'export DARKFI_TOR_LOCAL_ADDRESS=\"tcp://127.0.0.1:8080\"\'");
let hurl = Url::parse(&hurl).unwrap();
let onion = var("DARKFI_TOR_PUBLIC_ADDRESS").expect(
"Please set the env var DARKFI_TOR_PUBLIC_ADDRESS to the configured onion address. \
For example: \'export DARKFI_TOR_PUBLIC_ADDRESS=\"tor://abcdefghij234567.onion\"\'",
);
let tor = TorTransport::new(url, None).unwrap();
let listener = tor.clone().listen_on(hurl).unwrap().await.unwrap();
task::spawn(async move {
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await {
let stream = stream.unwrap();
let (reader, writer) = &mut (&stream, &stream);
io::copy(reader, writer).await.unwrap();
}
});
let payload = b"ohai tor";
let url = Url::parse(&onion).unwrap();
let mut client = tor.dial(url, None).unwrap().await.unwrap();
client.write_all(payload).await.unwrap();
let mut buf = vec![0_u8; 8];
client.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, payload);
}
#[async_std::test]
#[ignore]
async fn tor_transport_with_control() {
let auth_cookie = var("DARKFI_TOR_COOKIE").expect(
"Please set the env var DARKFI_TOR_COOKIE to the configured tor cookie file. \
For example: \'export DARKFI_TOR_COOKIE=\"/var/lib/tor/control_auth_cookie\"\'",
);
let auth_cookie = hex::encode(fs::read(auth_cookie).unwrap());
let socks_url = Url::parse("socks5://127.0.0.1:9050").unwrap();
let torc_url = Url::parse("tcp://127.0.0.1:9051").unwrap();
let local_url = Url::parse("tcp://127.0.0.1:8787").unwrap();
let tor = TorTransport::new(socks_url, Some((torc_url, auth_cookie))).unwrap();
// generate EHS pointing to local address
let hurl = tor.create_ehs(local_url.clone()).unwrap();
let listener = tor.clone().listen_on(local_url).unwrap().await.unwrap();
task::spawn(async move {
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await {
let stream = stream.unwrap();
let (reader, writer) = &mut (&stream, &stream);
io::copy(reader, writer).await.unwrap();
}
});
let payload = b"ohai tor";
let mut client = tor.dial(hurl, None).unwrap().await.unwrap();
client.write_all(payload).await.unwrap();
let mut buf = vec![0_u8; 8];
client.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, payload);
}
#[async_std::test]
#[should_panic(expected = "Socks5Error(ReplyError(HostUnreachable))")]
#[ignore]
async fn tor_transport_with_control_dropped() {
let auth_cookie = var("DARKFI_TOR_COOKIE").expect(
"Please set the env var DARKFI_TOR_COOKIE to the configured tor cookie file. \
For example: \'export DARKFI_TOR_COOKIE=\"/var/lib/tor/control_auth_cookie\"\'",
);
let auth_cookie = hex::encode(fs::read(auth_cookie).unwrap());
let socks_url = Url::parse("socks5://127.0.0.1:9050").unwrap();
let torc_url = Url::parse("tcp://127.0.0.1:9051").unwrap();
let local_url = Url::parse("tcp://127.0.0.1:8787").unwrap();
let hurl;
// We create a new scope for the Transport, to see if when we drop it, the host is still reachable
{
let tor = TorTransport::new(socks_url.clone(), Some((torc_url, auth_cookie))).unwrap();
// generate EHS pointing to local address
hurl = tor.create_ehs(local_url.clone()).unwrap();
// Nothing is listening, but the host is reachable.
// In this case, dialing should fail with Socks5Error(ReplyError(GeneralFailure));
// And not with Socks5Error(ReplyError(HostUnreachable))
}
let tor_client = TorTransport::new(socks_url, None).unwrap();
// Try to reach the host
let _client = tor_client.dial(hurl, None).unwrap().await.unwrap();
}
#[async_std::test]
#[ignore]
async fn nym_transport() {
let target_url = Url::parse("nym://127.0.0.1:25553").unwrap();
let nym = NymTransport::new().unwrap();
let listener = nym.clone().listen_on(target_url.clone()).unwrap().await.unwrap();
task::spawn(async move {
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await {
let stream = stream.unwrap();
let (reader, writer) = &mut (&stream, &stream);
io::copy(reader, writer).await.unwrap();
}
});
let payload = b"ohai nym";
let mut client = nym.dial(target_url, None).unwrap().await.unwrap();
client.write_all(payload).await.unwrap();
let mut buf = vec![0_u8; 8];
client.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, payload);
}
#[async_std::test]
#[ignore]
async fn nym_tls_transport() {
let target_url = Url::parse("nym+tls://127.0.0.1:25553").unwrap();
let nym = NymTransport::new().unwrap();
let listener = nym.clone().listen_on(target_url.clone()).unwrap().await.unwrap();
let (acceptor, listener) = nym.clone().upgrade_listener(listener).unwrap().await.unwrap();
task::spawn(async move {
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await {
let stream = stream.unwrap();
let stream = acceptor.accept(stream).await.unwrap();
let (mut reader, mut writer) = smol::io::split(stream);
io::copy(&mut reader, &mut writer).await.unwrap();
}
});
let payload = b"ohai nymtls";
let client = nym.clone().dial(target_url, None).unwrap().await.unwrap();
let mut client = nym.upgrade_dialer(client).unwrap().await.unwrap();
client.write_all(payload).await.unwrap();
let mut buf = vec![0_u8; 11];
client.read_exact(&mut buf).await.unwrap(); client.read_exact(&mut buf).await.unwrap();
assert_eq!(buf, payload); assert_eq!(buf, payload);