Skip to content

Commit

Permalink
Add ckb-onion integration test
Browse files Browse the repository at this point in the history
Signed-off-by: Eval EXEC <[email protected]>
  • Loading branch information
eval-exec committed Jan 21, 2025
1 parent db6a1d6 commit c0f57bb
Show file tree
Hide file tree
Showing 11 changed files with 516 additions and 8 deletions.
1 change: 1 addition & 0 deletions ckb-bin/src/subcommand/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ckb_build_info::Version;
use ckb_launcher::Launcher;
use ckb_logger::info;
use ckb_logger::warn;
use ckb_network::multiaddr::Multiaddr;
use ckb_resource::{Resource, TemplateContext};

use ckb_stop_handler::{broadcast_exit_signals, wait_all_ckb_services_exit};
Expand Down
20 changes: 16 additions & 4 deletions network/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ impl NetworkState {
config.peer_store_path(),
));
info!("Loaded the peer store.");
if let Some(ref proxy_url) = config.proxy_config.proxy_url {

if let Some(ref proxy_url) = config.proxy.proxy_url {
proxy::check_proxy_url(proxy_url).map_err(|reason| Error::Config(reason))?;
}

Expand Down Expand Up @@ -348,6 +349,12 @@ impl NetworkState {
.collect()
}

/// After onion service created,
/// ckb use this method to add onion address to public_addr
pub fn add_public_addr(&self, addr: Multiaddr) {
self.public_addrs.write().insert(addr);
}

pub(crate) fn connection_status(&self) -> ConnectionStatus {
self.peer_registry.read().connection_status()
}
Expand Down Expand Up @@ -996,10 +1003,10 @@ impl NetworkService {
if init.is_ready() {
break;
}
let proxy_config_enable = config.proxy_config.proxy_url.is_some();
let proxy_config_enable = config.proxy.proxy_url.is_some();
service_builder = service_builder
.tcp_proxy_config(config.proxy_config.proxy_url.clone())
.tcp_onion_config(config.onion_config.onion_server.clone());
.tcp_proxy_config(config.proxy.proxy_url.clone())
.tcp_onion_config(config.onion.onion_server.clone());

match find_type(multi_addr) {
TransportType::Tcp => {
Expand Down Expand Up @@ -1329,6 +1336,11 @@ impl NetworkController {
self.network_state.add_node(&self.p2p_control, address)
}

/// Add a public_addr to NetworkState.public_addrs
pub fn add_public_addr(&self, public_addr: Multiaddr) {
self.network_state.add_public_addr(public_addr)
}

/// Disconnect session with peer id
pub fn remove_node(&self, peer_id: &PeerId) {
if let Some(session_id) = self
Expand Down
1 change: 1 addition & 0 deletions test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ fn all_specs() -> Vec<Box<dyn Spec>> {
Box::new(CheckVmBExtension),
Box::new(RandomlyKill),
Box::new(SyncChurn),
Box::new(TorService),
];
specs.shuffle(&mut thread_rng());
specs
Expand Down
2 changes: 2 additions & 0 deletions test/src/specs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod p2p;
mod relay;
mod rpc;
mod sync;
mod tor;
mod tx_pool;

pub use alert::*;
Expand All @@ -20,6 +21,7 @@ pub use p2p::*;
pub use relay::*;
pub use rpc::*;
pub use sync::*;
pub use tor::*;
pub use tx_pool::*;

use crate::Node;
Expand Down
3 changes: 3 additions & 0 deletions test/src/specs/tor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod tor;

pub use tor::*;
107 changes: 107 additions & 0 deletions test/src/specs/tor/tor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use crate::utils::find_available_port;
use crate::{Node, Spec};
use ckb_channel::{Receiver, Sender};
use ckb_logger::{error, info, warn};
use std::process::Child;
use std::sync::Arc;

#[derive(Debug)]
struct TorServer {
tor_command_path: String,
socks_port: u16,
control_port: u16,
}

impl TorServer {
pub fn new() -> Self {
TorServer {
tor_command_path: std::option_env!("TOR_COMMAND_PATH")
.unwrap_or("tor")
.to_string(),
socks_port: find_available_port(),
control_port: find_available_port(),
}
}

fn build_tor_args(&self) -> Vec<String> {
vec![
"--SocksPort".to_string(),
self.socks_port.to_string(),
"--ControlPort".to_string(),
self.control_port.to_string(),
]
}

fn tor_start(&self) -> Child {
let mut cmd = std::process::Command::new(&self.tor_command_path);
let cmd = cmd.args(self.build_tor_args().clone());
let child = cmd.spawn().unwrap();
info!("tor started:({:?}) ; pid: {}", &self, child.id());
child
}
}

// create a sender and receiver for tor_server signal
static TOR_SERVER_PROCESS: std::sync::LazyLock<std::sync::Mutex<Option<Child>>> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(None));

struct TorServer_Guard {}

impl Drop for TorServer_Guard {
fn drop(&mut self) {
let mut child = TOR_SERVER_PROCESS.lock().unwrap();
let child = child.as_mut().unwrap();
info!("killing tor server... {}", child.id());
match child.kill() {
Ok(_) => {
info!("tor server exit success");
}
Err(e) => {
error!("tor server exit failed: {:?}", e);
}
};
}
}

pub struct TorService;

impl Spec for TorService {
crate::setup!(num_nodes: 1);

fn before_run(&self) -> Vec<Node> {
let tor_server = TorServer::new();
let tor_server_process = tor_server.tor_start();
*TOR_SERVER_PROCESS.lock().unwrap() = Some(tor_server_process);

std::thread::sleep(std::time::Duration::from_secs(5));

let mut node0 = Node::new(self.name(), "node0");
node0.modify_app_config(|config: &mut ckb_app_config::CKBAppConfig| {
config.network.onion.listen_on_onion = true;
config.network.onion.onion_server =
Some(format!("127.0.0.1:{}", tor_server.socks_port));
config.network.onion.tor_controller = format!("127.0.0.1:{}", tor_server.control_port);
});

node0.start();

vec![node0]
}

fn run(&self, nodes: &mut Vec<Node>) {
// when _tor_server_guard dropped, the tor server will be killed by Drop
let _tor_server_guard = TorServer_Guard {};

let node = &nodes[0];

let rpc_client = node.rpc_client();
let node_info = rpc_client.local_node_info();

let contains_onion_addr = node_info.addresses.iter().any(|addr| {
// check contains the onion address
info!("addr: {:?}", addr.address);
addr.address.contains("/onion3")
});
assert!(contains_onion_addr, "node should contains onion address");
}
}
34 changes: 32 additions & 2 deletions util/app-config/src/configs/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,32 @@ pub struct ProxyConfig {

/// Onion related config options
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct OnionConfig {
// like: socks5://username:[email protected]:1080
pub onion_url: Option<String>,
// Automatically create Tor onion service, default: true
#[serde(default = "default_listen_on_onion")]
pub listen_on_onion: bool,
// onion service target, if CKB's p2p listen address not on default 127.0.0.1:8115, you should set this
pub onion_service_target: Option<String>,
// Tor server url: like: 127.0.0.1:9050
pub onion_server: Option<String>,
// path to store onion private key, default is ./data/network/onion/onion_private_key
pub onion_private_key_path: Option<String>,
// tor controllr url, example: 127.0.0.1:9050
#[serde(default = "default_tor_controller")]
pub tor_controller: String,
// tor controller hashed password
pub tor_password: Option<String>,
}

/// By default, allow ckb to listen on onion address
const fn default_listen_on_onion() -> bool {
true
}

/// By default, use tor controller on "127.0.0.1:9051"
fn default_tor_controller() -> String {
"127.0.0.1:9051".to_string()
}

/// Chain synchronization config options.
Expand Down Expand Up @@ -271,6 +294,13 @@ impl Config {
path
}

/// Gets the onion network private key path.
pub fn onion_private_key_path(&self) -> PathBuf {
let mut path = self.path.clone();
path.push("onion_private_key");
path
}

/// Gets the peer store path.
pub fn peer_store_path(&self) -> PathBuf {
let mut path = self.path.clone();
Expand Down
103 changes: 101 additions & 2 deletions util/launcher/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! ckb launcher is helps to launch ckb node.
use ckb_app_config::{
BlockAssemblerConfig, ExitCode, RpcConfig, RpcModule, RunArgs, SupportProtocol,
BlockAssemblerConfig, ExitCode, RpcConfig, RpcModule, RunArgs, SupportProtocol, Url,
};
use ckb_async_runtime::Handle;
use ckb_block_filter::filter::BlockFilter as BlockFilterService;
Expand All @@ -12,13 +12,15 @@ use ckb_chain::ChainController;
use ckb_channel::Receiver;
use ckb_jsonrpc_types::ScriptHashType;
use ckb_light_client_protocol_server::LightClientProtocol;
use ckb_logger::info;
use ckb_logger::internal::warn;
use ckb_logger::{error, info};
use ckb_network::Error;
use ckb_network::{
network::TransportType, observe_listen_port_occupancy, CKBProtocol, Flags, NetworkController,
NetworkService, NetworkState, SupportProtocols,
};
use ckb_network_alert::alert_relayer::AlertRelayer;
use ckb_onion::OnionServiceConfig;
use ckb_resource::Resource;
use ckb_rpc::{RpcServer, ServiceBuilder};
use ckb_shared::shared_builder::{SharedBuilder, SharedPackage};
Expand All @@ -29,6 +31,7 @@ use ckb_tx_pool::service::TxVerificationResult;
use ckb_types::prelude::*;
use ckb_verification::GenesisVerifier;
use ckb_verification_traits::Verifier;
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;

const SECP256K1_BLAKE160_SIGHASH_ALL_ARG_LEN: usize = 20;
Expand Down Expand Up @@ -266,6 +269,98 @@ impl Launcher {
}
}

/// Start onion service
pub fn start_onion_service(&self, network_controller: NetworkController) -> Result<(), Error> {
if !self.args.config.network.onion.listen_on_onion {
info!("onion_config.listen_on_onion is false, CKB won't listen on the onion hidden netork");
return Ok(());
}
let onion_config = self.args.config.network.onion.clone();
let onion_server: String = {
match (
onion_config.onion_server,
self.args.config.network.proxy.proxy_url.clone(),
) {
(Some(onion_server), _) => onion_server,
(None, Some(proxy_url)) => {
let proxy_url = Url::parse(&proxy_url)
.map_err(|err| Error::Config(format!("parse proxy_url failed: {}", err)))?;
match (proxy_url.host_str(), proxy_url.port()) {
(Some(host), Some(port)) => format!("{}:{}", host, port),
_ => {
error!("CKB tried to use the proxy url: {proxy_url} as onion server, but failed to parse it");
return Err(Error::Config("Failed to parse proxy url".to_string()));
}
}
}
_ => {
info!("Neither onion_server nor proxy_url is set in the config file, CKB won't listen on the onion hidden network");
return Ok(());
}
}
};
let onion_service_target: SocketAddr = {
match onion_config.onion_service_target {
Some(onion_service_target) => onion_service_target.parse().map_err(|err| {
Error::Config(format!("Failed to parse onion_service_target: {}", err))
})?,
None => SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8115),
}
};

{
// check tor_controller is listening
let tor_controller_addr = onion_config.tor_controller.parse().map_err(|err| {
Error::Config(format!("Failed to parse tor_controller address: {}", err))
})?;
match std::net::TcpStream::connect_timeout(
&tor_controller_addr,
std::time::Duration::from_secs(2),
) {
Ok(c) => {
info!(
"CKB has confirmed that onion_conifg.tor_controller is listening on {}, trying to listen on the onion hidden network by the tor_controller",
onion_config.tor_controller
);
}
Err(err) => {
eprintln!("tor_controller is not listening on {}, CKB won't try to listen on the onion hidden network", tor_controller_addr);
return Ok(());
}
}
}

let onion_service_config: OnionServiceConfig = OnionServiceConfig {
onion_server,
onion_private_key_path: onion_config.onion_private_key_path.unwrap_or(
self.args
.config
.network
.onion_private_key_path()
.display()
.to_string(),
),
tor_controller: onion_config.tor_controller,
tor_password: onion_config.tor_password,
onion_service_target,
};
let onion_service = ckb_onion::onion_service::OnionService::new(
self.async_handle.clone(),
onion_service_config,
)
.map_err(|err| Error::Config(format!("Failed to create onion service: {}", err)))?;
self.async_handle.spawn(async move {
match onion_service.start().await {
Ok(onion_service_addr) => {
info!("CKB has started listening on the onion hidden network, the onion service address is: {}", onion_service_addr);
network_controller.add_public_addr(onion_service_addr);
},
Err(err) => error!("CKB failed to start listening on the onion hidden network: {}", err),
}
});
Ok(())
}

/// Start network service and rpc serve
pub fn start_network_and_rpc(
&self,
Expand Down Expand Up @@ -445,6 +540,10 @@ impl Launcher {

let _rpc = RpcServer::new(rpc_config, io_handler, self.rpc_handle.clone());

if let Err(err) = self.start_onion_service(network_controller.clone()) {
error!("Failed to start onion service: {}", err);
}

network_controller
}
}
Loading

0 comments on commit c0f57bb

Please sign in to comment.