Merge commit '10025f9fa5503865918cbae2af5366afe7fd7c54' as 'components/mycelium'
This commit is contained in:
1
components/mycelium/myceliumd/.gitignore
vendored
Normal file
1
components/mycelium/myceliumd/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
4419
components/mycelium/myceliumd/Cargo.lock
generated
Normal file
4419
components/mycelium/myceliumd/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
components/mycelium/myceliumd/Cargo.toml
Normal file
40
components/mycelium/myceliumd/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "myceliumd"
|
||||
version = "0.6.1"
|
||||
edition = "2021"
|
||||
license-file = "../LICENSE"
|
||||
readme = "./README.md"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "mycelium"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.41", features = ["derive"] }
|
||||
tracing = { version = "0.1.41", features = ["release_max_level_debug"] }
|
||||
tracing-logfmt = { version = "0.3.5", features = ["ansi_logs"] }
|
||||
tracing-subscriber = { version = "0.3.19", features = [
|
||||
"env-filter",
|
||||
"nu-ansi-term",
|
||||
] }
|
||||
mycelium = { path = "../mycelium", features = ["message"] }
|
||||
mycelium-metrics = { path = "../mycelium-metrics", features = ["prometheus"] }
|
||||
mycelium-cli = { path = "../mycelium-cli/", features = ["message"] }
|
||||
mycelium-api = { path = "../mycelium-api", features = ["message"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = { version = "1.46.1", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
] }
|
||||
reqwest = { version = "0.12.9", default-features = false, features = ["json"] }
|
||||
base64 = "0.22.1"
|
||||
prettytable-rs = "0.10.0"
|
||||
urlencoding = "2.1.3"
|
||||
byte-unit = "5.1.6"
|
||||
config = "0.15.13"
|
||||
dirs = "6.0.0"
|
||||
toml = "0.9.2"
|
||||
16
components/mycelium/myceliumd/README.md
Normal file
16
components/mycelium/myceliumd/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Myceliumd
|
||||
|
||||
This is the main binary to use for joining a/the public [`mycelium`] network. You can
|
||||
either get the latest release from [the GitHub release page](https://github.com/threefoldtech/myceliumd/releases/latest),
|
||||
or build the code yourself here. While we intend to keep the master branch as stable,
|
||||
i.e. a build from latest master should succeed, this is not a hard guarantee. Additionally,
|
||||
the master branch might not be compatible with the latest release, in case of a
|
||||
breaking change.
|
||||
|
||||
Building, with the rust toolchain installed, can be done by running `cargo build`
|
||||
in this directory. For compatibility reasons, the binary is renamed to `mycelium`.
|
||||
Optionally, the `--release` flag can be used when building. This is recommended
|
||||
if you are not just developing.
|
||||
|
||||
For more information about the project, please refer to [the README file in the
|
||||
repository root](../README.md).
|
||||
805
components/mycelium/myceliumd/src/main.rs
Normal file
805
components/mycelium/myceliumd/src/main.rs
Normal file
@@ -0,0 +1,805 @@
|
||||
use std::io::{self, Read};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
error::Error,
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use mycelium::message::TopicConfig;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
#[cfg(target_family = "unix")]
|
||||
use tokio::signal::{self, unix::SignalKind};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crypto::PublicKey;
|
||||
use mycelium::endpoint::Endpoint;
|
||||
use mycelium::{crypto, Node};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
/// The default port on the underlay to listen on for incoming TCP connections.
|
||||
const DEFAULT_TCP_LISTEN_PORT: u16 = 9651;
|
||||
/// The default port on the underlay to listen on for incoming Quic connections.
|
||||
const DEFAULT_QUIC_LISTEN_PORT: u16 = 9651;
|
||||
/// The default port to use for IPv6 link local peer discovery (UDP).
|
||||
const DEFAULT_PEER_DISCOVERY_PORT: u16 = 9650;
|
||||
/// The default listening address for the HTTP API.
|
||||
const DEFAULT_HTTP_API_SERVER_ADDRESS: SocketAddr =
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8989);
|
||||
/// The default listening address for the JSON-RPC API.
|
||||
const DEFAULT_JSONRPC_API_SERVER_ADDRESS: SocketAddr =
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8990);
|
||||
|
||||
const DEFAULT_KEY_FILE: &str = "priv_key.bin";
|
||||
|
||||
/// Default name of tun interface
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const TUN_NAME: &str = "mycelium";
|
||||
/// Default name of tun interface
|
||||
#[cfg(target_os = "macos")]
|
||||
const TUN_NAME: &str = "utun0";
|
||||
|
||||
/// The logging formats that can be selected.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
enum LoggingFormat {
|
||||
Compact,
|
||||
Logfmt,
|
||||
/// Same as Logfmt but with color statically disabled
|
||||
Plain,
|
||||
}
|
||||
|
||||
impl Display for LoggingFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
LoggingFormat::Compact => "compact",
|
||||
LoggingFormat::Logfmt => "logfmt",
|
||||
LoggingFormat::Plain => "plain",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for LoggingFormat {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"compact" => LoggingFormat::Compact,
|
||||
"logfmt" => LoggingFormat::Logfmt,
|
||||
"plain" => LoggingFormat::Plain,
|
||||
_ => return Err("invalid logging format"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version)]
|
||||
struct Cli {
|
||||
/// Path to the private key file. This will be created if it does not exist. Default
|
||||
/// [priv_key.bin].
|
||||
#[arg(short = 'k', long = "key-file", global = true)]
|
||||
key_file: Option<PathBuf>,
|
||||
|
||||
// Configuration file
|
||||
#[arg(short = 'c', long = "config-file", global = true)]
|
||||
config_file: Option<PathBuf>,
|
||||
|
||||
/// Enable debug logging. Does nothing if `--silent` is set.
|
||||
#[arg(short = 'd', long = "debug", default_value_t = false)]
|
||||
debug: bool,
|
||||
|
||||
/// Disable all logs except error logs.
|
||||
#[arg(long = "silent", default_value_t = false)]
|
||||
silent: bool,
|
||||
|
||||
/// The logging format to use. `logfmt` and `compact` is supported.
|
||||
#[arg(long = "log-format", default_value_t = LoggingFormat::Compact)]
|
||||
logging_format: LoggingFormat,
|
||||
|
||||
#[clap(flatten)]
|
||||
node_args: NodeArguments,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Inspect a public key provided in hex format, or export the local public key if no key is
|
||||
/// given.
|
||||
Inspect {
|
||||
/// Output in json format.
|
||||
#[arg(long = "json")]
|
||||
json: bool,
|
||||
|
||||
/// The key to inspect.
|
||||
key: Option<String>,
|
||||
},
|
||||
|
||||
/// Actions on the message subsystem
|
||||
Message {
|
||||
#[command(subcommand)]
|
||||
command: MessageCommand,
|
||||
},
|
||||
|
||||
/// Actions related to peers (list, remove, add)
|
||||
Peers {
|
||||
#[command(subcommand)]
|
||||
command: PeersCommand,
|
||||
},
|
||||
|
||||
/// Actions related to routes (selected, fallback, queried, no route)
|
||||
Routes {
|
||||
#[command(subcommand)]
|
||||
command: RoutesCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MessageCommand {
|
||||
Send {
|
||||
/// Wait for a reply from the receiver.
|
||||
#[arg(short = 'w', long = "wait", default_value_t = false)]
|
||||
wait: bool,
|
||||
/// An optional timeout to wait for. This does nothing if the `--wait` flag is not set. If
|
||||
/// `--wait` is set and this flag isn't, wait forever for a reply.
|
||||
#[arg(long = "timeout")]
|
||||
timeout: Option<u64>,
|
||||
/// Optional topic of the message. Receivers can filter on this to only receive messages
|
||||
/// for a chosen topic.
|
||||
#[arg(short = 't', long = "topic")]
|
||||
topic: Option<String>,
|
||||
/// Optional file to use as message body.
|
||||
#[arg(long = "msg-path")]
|
||||
msg_path: Option<PathBuf>,
|
||||
/// Optional message ID to reply to.
|
||||
#[arg(long = "reply-to")]
|
||||
reply_to: Option<String>,
|
||||
/// Destination of the message, either a hex encoded public key, or an IPv6 address in the
|
||||
/// 400::/7 range.
|
||||
destination: String,
|
||||
/// The message to send. This is required if `--msg_path` is not set
|
||||
message: Option<String>,
|
||||
},
|
||||
Receive {
|
||||
/// An optional timeout to wait for a message. If this is not set, wait forever.
|
||||
#[arg(long = "timeout")]
|
||||
timeout: Option<u64>,
|
||||
/// Optional topic of the message. Only messages with this topic will be received by this
|
||||
/// command.
|
||||
#[arg(short = 't', long = "topic")]
|
||||
topic: Option<String>,
|
||||
/// Optional file in which the message body will be saved.
|
||||
#[arg(long = "msg-path")]
|
||||
msg_path: Option<PathBuf>,
|
||||
/// Don't print the metadata
|
||||
#[arg(long = "raw")]
|
||||
raw: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum PeersCommand {
|
||||
/// List the connected peers
|
||||
List {
|
||||
/// Print the peers list in JSON format
|
||||
#[arg(long = "json", default_value_t = false)]
|
||||
json: bool,
|
||||
},
|
||||
/// Add peer(s)
|
||||
Add { peers: Vec<String> },
|
||||
/// Remove peer(s)
|
||||
Remove { peers: Vec<String> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum RoutesCommand {
|
||||
/// Print all selected routes
|
||||
Selected {
|
||||
/// Print selected routes in JSON format
|
||||
#[arg(long = "json", default_value_t = false)]
|
||||
json: bool,
|
||||
},
|
||||
/// Print all fallback routes
|
||||
Fallback {
|
||||
/// Print fallback routes in JSON format
|
||||
#[arg(long = "json", default_value_t = false)]
|
||||
json: bool,
|
||||
},
|
||||
/// Print the currently queried subnets
|
||||
Queried {
|
||||
/// Print queried subnets in JSON format
|
||||
#[arg(long = "json", default_value_t = false)]
|
||||
json: bool,
|
||||
},
|
||||
/// Print all subnets which are explicitly marked as not having a route
|
||||
NoRoute {
|
||||
/// Print subnets in JSON format
|
||||
#[arg(long = "json", default_value_t = false)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct NodeArguments {
|
||||
/// Peers to connect to.
|
||||
#[arg(long = "peers", num_args = 1..)]
|
||||
static_peers: Vec<Endpoint>,
|
||||
|
||||
/// Port to listen on for tcp connections.
|
||||
#[arg(short = 't', long = "tcp-listen-port", default_value_t = DEFAULT_TCP_LISTEN_PORT)]
|
||||
tcp_listen_port: u16,
|
||||
|
||||
/// Disable quic protocol for connecting to peers
|
||||
#[arg(long = "disable-quic", default_value_t = false)]
|
||||
disable_quic: bool,
|
||||
|
||||
/// Port to listen on for quic connections.
|
||||
#[arg(short = 'q', long = "quic-listen-port", default_value_t = DEFAULT_QUIC_LISTEN_PORT)]
|
||||
quic_listen_port: u16,
|
||||
|
||||
/// Port to use for link local peer discovery. This uses the UDP protocol.
|
||||
#[arg(long = "peer-discovery-port", default_value_t = DEFAULT_PEER_DISCOVERY_PORT)]
|
||||
peer_discovery_port: u16,
|
||||
|
||||
/// Disable peer discovery.
|
||||
///
|
||||
/// If this flag is passed, the automatic link local peer discovery will not be enabled, and
|
||||
/// peers must be configured manually. If this is disabled on all local peers, communication
|
||||
/// between them will go over configured external peers.
|
||||
#[arg(long = "disable-peer-discovery", default_value_t = false)]
|
||||
disable_peer_discovery: bool,
|
||||
|
||||
/// Address of the HTTP API server.
|
||||
#[arg(long = "api-addr", default_value_t = DEFAULT_HTTP_API_SERVER_ADDRESS)]
|
||||
api_addr: SocketAddr,
|
||||
|
||||
/// Address of the JSON-RPC API server.
|
||||
#[arg(long = "jsonrpc-addr", default_value_t = DEFAULT_JSONRPC_API_SERVER_ADDRESS)]
|
||||
jsonrpc_addr: SocketAddr,
|
||||
|
||||
/// Run without creating a TUN interface.
|
||||
///
|
||||
/// The system will participate in the network as usual, but won't be able to send out L3
|
||||
/// packets. Inbound L3 traffic will be silently discarded. The message subsystem will still
|
||||
/// work however.
|
||||
#[arg(long = "no-tun", default_value_t = false)]
|
||||
no_tun: bool,
|
||||
|
||||
/// Name to use for the TUN interface, if one is created.
|
||||
///
|
||||
/// Setting this only matters if a TUN interface is actually created, i.e. if the `--no-tun`
|
||||
/// flag is **not** set. The name set here must be valid for the current platform, e.g. on OSX,
|
||||
/// the name must start with `utun` and be followed by digits.
|
||||
#[arg(long = "tun-name")]
|
||||
tun_name: Option<String>,
|
||||
|
||||
/// The address on which to expose prometheus metrics, if desired.
|
||||
///
|
||||
/// Setting this flag will attempt to start an HTTP server on the provided address, to serve
|
||||
/// prometheus metrics on the /metrics endpoint. If this flag is not set, metrics are also not
|
||||
/// collected.
|
||||
#[arg(long = "metrics-api-address")]
|
||||
metrics_api_address: Option<SocketAddr>,
|
||||
|
||||
/// The firewall mark to set on the mycelium sockets.
|
||||
///
|
||||
/// This allows to identify packets that contain encapsulated mycelium packets so that
|
||||
/// different routing policies can be applied to them.
|
||||
/// This option only has an effect on Linux.
|
||||
#[arg(long = "firewall-mark")]
|
||||
firewall_mark: Option<u32>,
|
||||
|
||||
/// The amount of worker tasks to spawn to handle updates.
|
||||
///
|
||||
/// By default, updates are processed on a single task only. This is sufficient for most use
|
||||
/// cases. In case you notice that the node can't keep up with the incoming updates (typically
|
||||
/// because you are running a public node with a lot of connections), this value can be
|
||||
/// increased to process updates in parallel.
|
||||
#[arg(long = "update-workers", default_value_t = 1)]
|
||||
update_workers: usize,
|
||||
|
||||
/// The topic configuration.
|
||||
///
|
||||
/// A .toml file containing topic configuration. This is a default action in case the topic is
|
||||
/// not listed, and an explicit whitelist for allowed subnets/ips which are otherwise allowed
|
||||
/// to use a topic.
|
||||
#[arg(long = "topic-config")]
|
||||
topic_config: Option<PathBuf>,
|
||||
|
||||
/// The cache directory for the mycelium CDN module
|
||||
///
|
||||
/// This directory will be used to cache reconstructed content blocks which were loaded through
|
||||
/// the CDN functionallity for faster access next time.
|
||||
#[arg(long = "cdn-cache")]
|
||||
cdn_cache: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MergedNodeConfig {
|
||||
peers: Vec<Endpoint>,
|
||||
tcp_listen_port: u16,
|
||||
disable_quic: bool,
|
||||
quic_listen_port: u16,
|
||||
peer_discovery_port: u16,
|
||||
disable_peer_discovery: bool,
|
||||
api_addr: SocketAddr,
|
||||
jsonrpc_addr: SocketAddr,
|
||||
no_tun: bool,
|
||||
tun_name: String,
|
||||
metrics_api_address: Option<SocketAddr>,
|
||||
firewall_mark: Option<u32>,
|
||||
update_workers: usize,
|
||||
topic_config: Option<PathBuf>,
|
||||
cdn_cache: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
struct MyceliumConfig {
|
||||
#[serde(deserialize_with = "deserialize_optional_endpoint_str_from_toml")]
|
||||
peers: Option<Vec<Endpoint>>,
|
||||
tcp_listen_port: Option<u16>,
|
||||
disable_quic: Option<bool>,
|
||||
quic_listen_port: Option<u16>,
|
||||
no_tun: Option<bool>,
|
||||
tun_name: Option<String>,
|
||||
disable_peer_discovery: Option<bool>,
|
||||
peer_discovery_port: Option<u16>,
|
||||
api_addr: Option<SocketAddr>,
|
||||
jsonrpc_addr: Option<SocketAddr>,
|
||||
metrics_api_address: Option<SocketAddr>,
|
||||
firewall_mark: Option<u32>,
|
||||
update_workers: Option<usize>,
|
||||
topic_config: Option<PathBuf>,
|
||||
cdn_cache: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Init default configuration
|
||||
let mut mycelium_config = MyceliumConfig::default();
|
||||
|
||||
// Load configuration file
|
||||
if let Some(config_file_path) = &cli.config_file {
|
||||
if Path::new(config_file_path).exists() {
|
||||
let config = config::Config::builder()
|
||||
.add_source(config::File::new(
|
||||
config_file_path.to_str().unwrap(),
|
||||
config::FileFormat::Toml,
|
||||
))
|
||||
.build()?;
|
||||
|
||||
mycelium_config = config.try_deserialize()?;
|
||||
} else {
|
||||
let error_msg = format!("Config file {config_file_path:?} not found");
|
||||
return Err(io::Error::new(io::ErrorKind::NotFound, error_msg).into());
|
||||
}
|
||||
} else if let Some(mut conf) = dirs::config_dir() {
|
||||
// Windows: %APPDATA%/ThreeFold Tech/Mycelium/mycelium.conf
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
conf = conf
|
||||
.join("ThreeFold Tech")
|
||||
.join("Mycelium")
|
||||
.join("mycelium.toml")
|
||||
};
|
||||
// Linux: $HOME/.config/mycelium/mycelium.conf
|
||||
#[allow(clippy::unnecessary_operation)]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
conf = conf.join("mycelium").join("mycelium.toml")
|
||||
};
|
||||
// MacOS: $HOME/Library/Application Support/ThreeFold Tech/Mycelium/mycelium.conf
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
conf = conf
|
||||
.join("ThreeFold Tech")
|
||||
.join("Mycelium")
|
||||
.join("mycelium.toml")
|
||||
};
|
||||
|
||||
if conf.exists() {
|
||||
info!(
|
||||
conf_dir = conf.to_str().unwrap(),
|
||||
"Mycelium is starting with configuration file",
|
||||
);
|
||||
let config = config::Config::builder()
|
||||
.add_source(config::File::new(
|
||||
conf.to_str().unwrap(),
|
||||
config::FileFormat::Toml,
|
||||
))
|
||||
.build()?;
|
||||
mycelium_config = config.try_deserialize()?;
|
||||
}
|
||||
}
|
||||
|
||||
let level = if cli.silent {
|
||||
tracing::Level::ERROR
|
||||
} else if cli.debug {
|
||||
tracing::Level::DEBUG
|
||||
} else {
|
||||
tracing::Level::INFO
|
||||
};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
EnvFilter::builder()
|
||||
.with_default_directive(level.into())
|
||||
.from_env()
|
||||
.expect("invalid RUST_LOG"),
|
||||
)
|
||||
.with(
|
||||
(cli.logging_format == LoggingFormat::Compact)
|
||||
.then(|| tracing_subscriber::fmt::Layer::new().compact()),
|
||||
)
|
||||
.with((cli.logging_format == LoggingFormat::Logfmt).then(tracing_logfmt::layer))
|
||||
.with((cli.logging_format == LoggingFormat::Plain).then(|| {
|
||||
tracing_logfmt::builder()
|
||||
// Explicitly force color off
|
||||
.with_ansi_color(false)
|
||||
.layer()
|
||||
}))
|
||||
.init();
|
||||
|
||||
let key_path = cli
|
||||
.key_file
|
||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_KEY_FILE));
|
||||
|
||||
match cli.command {
|
||||
None => {
|
||||
let merged_config = merge_config(cli.node_args, mycelium_config);
|
||||
let topic_config = merged_config.topic_config.as_ref().and_then(|path| {
|
||||
let mut content = String::new();
|
||||
let mut file = std::fs::File::open(path).ok()?;
|
||||
file.read_to_string(&mut content).ok()?;
|
||||
toml::from_str::<TopicConfig>(&content).ok()
|
||||
});
|
||||
|
||||
if topic_config.is_some() {
|
||||
info!(path = ?merged_config.topic_config, "Loaded topic cofig");
|
||||
}
|
||||
|
||||
let node_keys = get_node_keys(&key_path).await?;
|
||||
let node_secret_key = if let Some((node_secret_key, _)) = node_keys {
|
||||
node_secret_key
|
||||
} else {
|
||||
warn!("Node key file {key_path:?} not found, generating new keys");
|
||||
let secret_key = crypto::SecretKey::new();
|
||||
save_key_file(&secret_key, &key_path).await?;
|
||||
secret_key
|
||||
};
|
||||
|
||||
let _api = if let Some(metrics_api_addr) = merged_config.metrics_api_address {
|
||||
let metrics = mycelium_metrics::PrometheusExporter::new();
|
||||
let config = mycelium::Config {
|
||||
node_key: node_secret_key,
|
||||
peers: merged_config.peers,
|
||||
no_tun: merged_config.no_tun,
|
||||
tcp_listen_port: merged_config.tcp_listen_port,
|
||||
quic_listen_port: if merged_config.disable_quic {
|
||||
None
|
||||
} else {
|
||||
Some(merged_config.quic_listen_port)
|
||||
},
|
||||
peer_discovery_port: if merged_config.disable_peer_discovery {
|
||||
None
|
||||
} else {
|
||||
Some(merged_config.peer_discovery_port)
|
||||
},
|
||||
tun_name: merged_config.tun_name,
|
||||
private_network_config: None,
|
||||
metrics: metrics.clone(),
|
||||
firewall_mark: merged_config.firewall_mark,
|
||||
update_workers: merged_config.update_workers,
|
||||
topic_config,
|
||||
cdn_cache: merged_config.cdn_cache,
|
||||
};
|
||||
metrics.spawn(metrics_api_addr);
|
||||
let node = Arc::new(Mutex::new(Node::new(config).await?));
|
||||
let http_api = mycelium_api::Http::spawn(node.clone(), merged_config.api_addr);
|
||||
|
||||
// Initialize the JSON-RPC server
|
||||
let rpc_api =
|
||||
mycelium_api::rpc::JsonRpc::spawn(node, merged_config.jsonrpc_addr).await;
|
||||
|
||||
(http_api, rpc_api)
|
||||
} else {
|
||||
let config = mycelium::Config {
|
||||
node_key: node_secret_key,
|
||||
peers: merged_config.peers,
|
||||
no_tun: merged_config.no_tun,
|
||||
tcp_listen_port: merged_config.tcp_listen_port,
|
||||
quic_listen_port: if merged_config.disable_quic {
|
||||
None
|
||||
} else {
|
||||
Some(merged_config.quic_listen_port)
|
||||
},
|
||||
peer_discovery_port: if merged_config.disable_peer_discovery {
|
||||
None
|
||||
} else {
|
||||
Some(merged_config.peer_discovery_port)
|
||||
},
|
||||
tun_name: merged_config.tun_name,
|
||||
private_network_config: None,
|
||||
metrics: mycelium_metrics::NoMetrics,
|
||||
firewall_mark: merged_config.firewall_mark,
|
||||
update_workers: merged_config.update_workers,
|
||||
topic_config,
|
||||
cdn_cache: merged_config.cdn_cache,
|
||||
};
|
||||
let node = Arc::new(Mutex::new(Node::new(config).await?));
|
||||
let http_api = mycelium_api::Http::spawn(node.clone(), merged_config.api_addr);
|
||||
|
||||
// Initialize the JSON-RPC server
|
||||
let rpc_api =
|
||||
mycelium_api::rpc::JsonRpc::spawn(node, merged_config.jsonrpc_addr).await;
|
||||
|
||||
(http_api, rpc_api)
|
||||
};
|
||||
|
||||
// TODO: put in dedicated file so we can only rely on certain signals on unix platforms
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
let mut sigint = signal::unix::signal(SignalKind::interrupt())
|
||||
.expect("Can install SIGINT handler");
|
||||
let mut sigterm = signal::unix::signal(SignalKind::terminate())
|
||||
.expect("Can install SIGTERM handler");
|
||||
|
||||
tokio::select! {
|
||||
_ = sigint.recv() => { }
|
||||
_ = sigterm.recv() => { }
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
||||
error!("Failed to wait for SIGINT: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(cmd) => match cmd {
|
||||
Command::Inspect { json, key } => {
|
||||
let node_keys = get_node_keys(&key_path).await?;
|
||||
let key = if let Some(key) = key {
|
||||
PublicKey::try_from(key.as_str())?
|
||||
} else if let Some((_, node_pub_key)) = node_keys {
|
||||
node_pub_key
|
||||
} else {
|
||||
error!("No key to inspect provided and no key found at {key_path:?}");
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"no key to inspect and key file not found",
|
||||
)
|
||||
.into());
|
||||
};
|
||||
mycelium_cli::inspect(key, json)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
Command::Message { command } => match command {
|
||||
MessageCommand::Send {
|
||||
wait,
|
||||
timeout,
|
||||
topic,
|
||||
msg_path,
|
||||
reply_to,
|
||||
destination,
|
||||
message,
|
||||
} => {
|
||||
return mycelium_cli::send_msg(
|
||||
destination,
|
||||
message,
|
||||
wait,
|
||||
timeout,
|
||||
reply_to,
|
||||
topic,
|
||||
msg_path,
|
||||
cli.node_args.api_addr,
|
||||
)
|
||||
.await
|
||||
}
|
||||
MessageCommand::Receive {
|
||||
timeout,
|
||||
topic,
|
||||
msg_path,
|
||||
raw,
|
||||
} => {
|
||||
return mycelium_cli::recv_msg(
|
||||
timeout,
|
||||
topic,
|
||||
msg_path,
|
||||
raw,
|
||||
cli.node_args.api_addr,
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
Command::Peers { command } => match command {
|
||||
PeersCommand::List { json } => {
|
||||
return mycelium_cli::list_peers(cli.node_args.api_addr, json).await;
|
||||
}
|
||||
PeersCommand::Add { peers } => {
|
||||
return mycelium_cli::add_peers(cli.node_args.api_addr, peers).await;
|
||||
}
|
||||
PeersCommand::Remove { peers } => {
|
||||
return mycelium_cli::remove_peers(cli.node_args.api_addr, peers).await;
|
||||
}
|
||||
},
|
||||
Command::Routes { command } => match command {
|
||||
RoutesCommand::Selected { json } => {
|
||||
return mycelium_cli::list_selected_routes(cli.node_args.api_addr, json).await;
|
||||
}
|
||||
RoutesCommand::Fallback { json } => {
|
||||
return mycelium_cli::list_fallback_routes(cli.node_args.api_addr, json).await;
|
||||
}
|
||||
RoutesCommand::Queried { json } => {
|
||||
return mycelium_cli::list_queried_subnets(cli.node_args.api_addr, json).await;
|
||||
}
|
||||
RoutesCommand::NoRoute { json } => {
|
||||
return mycelium_cli::list_no_route_entries(cli.node_args.api_addr, json).await;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_node_keys(
|
||||
key_path: &PathBuf,
|
||||
) -> Result<Option<(crypto::SecretKey, crypto::PublicKey)>, io::Error> {
|
||||
if key_path.exists() {
|
||||
let sk = load_key_file(key_path).await?;
|
||||
let pk = crypto::PublicKey::from(&sk);
|
||||
debug!("Loaded key file at {key_path:?}");
|
||||
Ok(Some((sk, pk)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_key_file<T>(path: &Path) -> Result<T, io::Error>
|
||||
where
|
||||
T: From<[u8; 32]>,
|
||||
{
|
||||
let mut file = File::open(path).await?;
|
||||
let mut secret_bytes = [0u8; 32];
|
||||
file.read_exact(&mut secret_bytes).await?;
|
||||
|
||||
Ok(T::from(secret_bytes))
|
||||
}
|
||||
|
||||
async fn save_key_file(key: &crypto::SecretKey, path: &Path) -> io::Result<()> {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
use tokio::fs::OpenOptions;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.mode(0o600) // rw by the owner, not readable by group or others
|
||||
.open(path)
|
||||
.await?;
|
||||
file.write_all(key.as_bytes()).await?;
|
||||
}
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
let mut file = File::create(path).await?;
|
||||
file.write_all(key.as_bytes()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn merge_config(cli_args: NodeArguments, file_config: MyceliumConfig) -> MergedNodeConfig {
|
||||
MergedNodeConfig {
|
||||
peers: if !cli_args.static_peers.is_empty() {
|
||||
cli_args.static_peers
|
||||
} else {
|
||||
file_config.peers.unwrap_or_default()
|
||||
},
|
||||
tcp_listen_port: if cli_args.tcp_listen_port != DEFAULT_TCP_LISTEN_PORT {
|
||||
cli_args.tcp_listen_port
|
||||
} else {
|
||||
file_config
|
||||
.tcp_listen_port
|
||||
.unwrap_or(DEFAULT_TCP_LISTEN_PORT)
|
||||
},
|
||||
disable_quic: cli_args.disable_quic || file_config.disable_quic.unwrap_or(false),
|
||||
quic_listen_port: if cli_args.quic_listen_port != DEFAULT_QUIC_LISTEN_PORT {
|
||||
cli_args.quic_listen_port
|
||||
} else {
|
||||
file_config
|
||||
.quic_listen_port
|
||||
.unwrap_or(DEFAULT_QUIC_LISTEN_PORT)
|
||||
},
|
||||
peer_discovery_port: if cli_args.peer_discovery_port != DEFAULT_PEER_DISCOVERY_PORT {
|
||||
cli_args.peer_discovery_port
|
||||
} else {
|
||||
file_config
|
||||
.peer_discovery_port
|
||||
.unwrap_or(DEFAULT_PEER_DISCOVERY_PORT)
|
||||
},
|
||||
disable_peer_discovery: cli_args.disable_peer_discovery
|
||||
|| file_config.disable_peer_discovery.unwrap_or(false),
|
||||
api_addr: if cli_args.api_addr != DEFAULT_HTTP_API_SERVER_ADDRESS {
|
||||
cli_args.api_addr
|
||||
} else {
|
||||
file_config
|
||||
.api_addr
|
||||
.unwrap_or(DEFAULT_HTTP_API_SERVER_ADDRESS)
|
||||
},
|
||||
jsonrpc_addr: if cli_args.jsonrpc_addr != DEFAULT_JSONRPC_API_SERVER_ADDRESS {
|
||||
cli_args.jsonrpc_addr
|
||||
} else {
|
||||
file_config
|
||||
.jsonrpc_addr
|
||||
.unwrap_or(DEFAULT_JSONRPC_API_SERVER_ADDRESS)
|
||||
},
|
||||
no_tun: cli_args.no_tun || file_config.no_tun.unwrap_or(false),
|
||||
tun_name: if let Some(tun_name_cli) = cli_args.tun_name {
|
||||
tun_name_cli
|
||||
} else if let Some(tun_name_config) = file_config.tun_name {
|
||||
tun_name_config
|
||||
} else {
|
||||
TUN_NAME.to_string()
|
||||
},
|
||||
metrics_api_address: cli_args
|
||||
.metrics_api_address
|
||||
.or(file_config.metrics_api_address),
|
||||
firewall_mark: cli_args.firewall_mark.or(file_config.firewall_mark),
|
||||
update_workers: if cli_args.update_workers != 1 {
|
||||
cli_args.update_workers
|
||||
} else {
|
||||
file_config.update_workers.unwrap_or(1)
|
||||
},
|
||||
topic_config: cli_args.topic_config.or(file_config.topic_config),
|
||||
cdn_cache: cli_args.cdn_cache.or(file_config.cdn_cache),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize an optional list of endpoints from TOML format. The endpoints can be provided
|
||||
/// either as a list `[...]`, or in case there is only 1 endpoint, it can also be provided as a
|
||||
/// single string element. If no value is provided, it returns None.
|
||||
fn deserialize_optional_endpoint_str_from_toml<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<Endpoint>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum StringOrVec {
|
||||
String(String),
|
||||
Vec(Vec<String>),
|
||||
}
|
||||
|
||||
Ok(match Option::<StringOrVec>::deserialize(deserializer)? {
|
||||
Some(StringOrVec::Vec(v)) => Some(
|
||||
v.into_iter()
|
||||
.map(|s| {
|
||||
<Endpoint as std::str::FromStr>::from_str(&s).map_err(serde::de::Error::custom)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
Some(StringOrVec::String(s)) => Some(vec![
|
||||
<Endpoint as std::str::FromStr>::from_str(&s).map_err(serde::de::Error::custom)?
|
||||
]),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user