Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Add CLI options
Alexis Sellier committed 3 years ago
commit 86059467036681b36a4b1f7952aaf55214f7a650
parent 116eaa79e6367e5790569e54324ad98ecde71963
10 files changed +150 -14
modified Cargo.lock
@@ -489,6 +489,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
+
name = "lexopt"
+
version = "0.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
+

+
[[package]]
name = "libc"
version = "0.2.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -808,6 +814,7 @@ dependencies = [
 "colored",
 "crossbeam-channel",
 "fastrand",
+
 "lexopt",
 "log",
 "nakamoto-net",
 "nakamoto-net-poll",
modified radicle-node/Cargo.toml
@@ -13,6 +13,7 @@ chrono = { version = "0.4.0" }
colored = { version = "1.9.0" }
crossbeam-channel = { version = "0.5.6" }
fastrand = { version = "1.8.0" }
+
lexopt = { version = "0.2.1" }
log = { version = "0.4.17", features = ["std"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
modified radicle-node/src/lib.rs
@@ -22,6 +22,7 @@ pub mod prelude {
    pub use crate::hash::Digest;
    pub use crate::identity::{Did, Id};
    pub use crate::service::filter::Filter;
+
    pub use crate::service::message::Address;
    pub use crate::service::{DisconnectReason, Envelope, Event, Message, Network, NodeId};
    pub use crate::storage::refs::Refs;
    pub use crate::storage::WriteStorage;
modified radicle-node/src/main.rs
@@ -1,15 +1,69 @@
use std::thread;
-
use std::{env, net};
+
use std::{env, net, process};

-
use radicle_node::{client, control};
+
use radicle_node::prelude::Address;
+
use radicle_node::{client, control, git, service};

type Reactor = nakamoto_net_poll::Reactor<net::TcpStream>;

+
#[derive(Debug)]
+
struct Options {
+
    connect: Vec<Address>,
+
    listen: Vec<net::SocketAddr>,
+
    git_url: git::Url,
+
}
+

+
impl Options {
+
    fn from_env() -> Result<Self, lexopt::Error> {
+
        use lexopt::prelude::*;
+
        let mut parser = lexopt::Parser::from_env();
+
        let mut connect = Vec::new();
+
        let mut listen = Vec::new();
+
        let mut git_url = None;
+

+
        while let Some(arg) = parser.next()? {
+
            match arg {
+
                Long("connect") => {
+
                    let addr = parser.value()?.parse()?;
+
                    connect.push(addr);
+
                }
+
                Long("listen") => {
+
                    let addr = parser.value()?.parse()?;
+
                    listen.push(addr);
+
                }
+
                Long("git-url") => {
+
                    let url = git::Url::from_bytes(parser.value()?.into_string()?.as_bytes())
+
                        .map_err(|e| format!("invalid URL: {}", e))?;
+
                    git_url = Some(url);
+
                }
+
                Long("help") => {
+
                    println!("usage: radicle-node [--connect <addr>]..");
+
                    process::exit(0);
+
                }
+
                _ => return Err(arg.unexpected()),
+
            }
+
        }
+
        Ok(Self {
+
            connect,
+
            listen,
+
            git_url: git_url.ok_or("a Git URL must be specified with `--git-url`")?,
+
        })
+
    }
+
}
+

fn main() -> anyhow::Result<()> {
+
    let options = Options::from_env()?;
    let profile = radicle::Profile::load()?;
    let client = client::Client::<Reactor>::new(profile)?;
    let handle = client.handle();
-
    let config = client::Config::default();
+
    let config = client::Config {
+
        service: service::Config {
+
            connect: options.connect,
+
            git_url: options.git_url,
+
            ..service::Config::default()
+
        },
+
        listen: options.listen,
+
    };
    let socket = env::var("RAD_SOCKET").unwrap_or_else(|_| control::DEFAULT_SOCKET_NAME.to_owned());

    let t1 = thread::spawn(move || control::listen(socket, handle));
modified radicle-node/src/service.rs
@@ -28,6 +28,7 @@ use crate::git;
use crate::git::Url;
use crate::identity::{Doc, Id};
use crate::service::config::ProjectTracking;
+
use crate::service::message::Address;
use crate::service::message::{NodeAnnouncement, RefsAnnouncement};
use crate::service::peer::{Session, SessionError, SessionState};
use crate::storage;
@@ -464,8 +465,9 @@ where
    }

    pub fn attempted(&mut self, addr: &std::net::SocketAddr) {
+
        let address = Address::from(*addr);
        let ip = addr.ip();
-
        let persistent = self.config.is_persistent(addr);
+
        let persistent = self.config.is_persistent(&address);
        let peer = self
            .sessions
            .entry(ip)
@@ -481,6 +483,7 @@ where
        link: Link,
    ) {
        let ip = addr.ip();
+
        let address = addr.into();

        debug!("Connected to {} ({:?})", ip, link);

@@ -505,7 +508,7 @@ where
        } else {
            self.sessions.insert(
                ip,
-
                Session::new(addr, Link::Inbound, self.config.is_persistent(&addr)),
+
                Session::new(addr, Link::Inbound, self.config.is_persistent(&address)),
            );
        }
    }
@@ -516,6 +519,7 @@ where
        reason: nakamoto::DisconnectReason<DisconnectReason>,
    ) {
        let since = self.local_time();
+
        let address = Address::from(*addr);
        let ip = addr.ip();

        debug!("Disconnected from {} ({})", ip, reason);
@@ -524,7 +528,7 @@ where
            peer.state = SessionState::Disconnected { since };

            // Attempt to re-connect to persistent peers.
-
            if self.config.is_persistent(addr) && peer.attempts() < MAX_CONNECTION_ATTEMPTS {
+
            if self.config.is_persistent(&address) && peer.attempts() < MAX_CONNECTION_ATTEMPTS {
                if reason.is_dial_err() {
                    return;
                }
modified radicle-node/src/service/config.rs
@@ -1,5 +1,3 @@
-
use std::net;
-

use crate::collections::HashSet;
use crate::git;
use crate::git::Url;
@@ -65,7 +63,7 @@ pub enum RemoteTracking {
pub struct Config {
    /// Peers to connect to on startup.
    /// Connections to these peers will be maintained.
-
    pub connect: Vec<net::SocketAddr>,
+
    pub connect: Vec<Address>,
    /// Peer-to-peer network.
    pub network: Network,
    /// Project tracking policy.
@@ -99,7 +97,7 @@ impl Default for Config {
}

impl Config {
-
    pub fn is_persistent(&self, addr: &net::SocketAddr) -> bool {
+
    pub fn is_persistent(&self, addr: &Address) -> bool {
        self.connect.contains(addr)
    }

modified radicle-node/src/service/message.rs
@@ -1,5 +1,8 @@
+
use std::str::FromStr;
use std::{fmt, io, net};

+
use thiserror::Error;
+

use crate::crypto;
use crate::git;
use crate::identity::Id;
@@ -24,6 +27,12 @@ pub type NodeFeatures = [u8; 32];
// TODO: We should check the length and charset when deserializing.
pub struct Hostname(String);

+
impl fmt::Display for Hostname {
+
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
        write!(f, "{}", self.0)
+
    }
+
}
+

/// Peer public protocol address.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Address {
@@ -59,6 +68,52 @@ impl From<net::SocketAddr> for Address {
    }
}

+
#[derive(Debug, Error)]
+
pub enum AddressParseError {
+
    #[error("unsupported address type `{0}`")]
+
    Unsupported(String),
+
}
+

+
impl FromStr for Address {
+
    type Err = AddressParseError;
+

+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        if let Ok(addr) = net::SocketAddr::from_str(s) {
+
            match addr.ip() {
+
                net::IpAddr::V4(ip) => Ok(Self::Ipv4 {
+
                    ip,
+
                    port: addr.port(),
+
                }),
+
                net::IpAddr::V6(ip) => Ok(Self::Ipv6 {
+
                    ip,
+
                    port: addr.port(),
+
                }),
+
            }
+
        } else {
+
            Err(Self::Err::Unsupported(s.to_owned()))
+
        }
+
    }
+
}
+

+
impl fmt::Display for Address {
+
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
        match self {
+
            Self::Ipv4 { ip, port } => {
+
                write!(f, "{}:{}", ip, port)
+
            }
+
            Self::Ipv6 { ip, port } => {
+
                write!(f, "{}:{}", ip, port)
+
            }
+
            Self::Hostname { host, port } => {
+
                write!(f, "{}:{}", host, port)
+
            }
+
            Self::Onion { key, port, .. } => {
+
                write!(f, "{}:{}", key, port)
+
            }
+
        }
+
    }
+
}
+

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subscribe {
    /// Subscribe to events matching this filter.
modified radicle-node/src/service/reactor.rs
@@ -44,9 +44,21 @@ impl Reactor {
    }

    /// Connect to a peer.
-
    pub fn connect(&mut self, addr: net::SocketAddr) {
+
    pub fn connect(&mut self, addr: impl Into<Address>) {
        // TODO: Make sure we don't try to connect more than once to the same address.
-
        self.io.push_back(Io::Connect(addr));
+
        match addr.into() {
+
            Address::Ipv4 { ip, port } => {
+
                self.io
+
                    .push_back(Io::Connect(net::SocketAddr::new(net::IpAddr::V4(ip), port)));
+
            }
+
            Address::Ipv6 { ip, port } => {
+
                self.io
+
                    .push_back(Io::Connect(net::SocketAddr::new(net::IpAddr::V6(ip), port)));
+
            }
+
            other => {
+
                log::error!("Unsupported address type `{}`", other);
+
            }
+
        }
    }

    /// Disconnect a peer.
modified radicle-node/src/test/peer.rs
@@ -125,6 +125,10 @@ where
        }
    }

+
    pub fn address(&self) -> Address {
+
        simulator::Peer::addr(self).into()
+
    }
+

    pub fn timestamp(&self) -> Timestamp {
        self.service.clock().timestamp()
    }
modified radicle-node/src/test/tests.rs
@@ -78,7 +78,7 @@ fn test_persistent_peer_connect() {
    let bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::empty());
    let eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::empty());
    let config = Config {
-
        connect: vec![bob.addr(), eve.addr()],
+
        connect: vec![bob.address(), eve.address()],
        ..Config::default()
    };
    let mut alice = Peer::config(
@@ -302,7 +302,7 @@ fn test_persistent_peer_reconnect() {
    let mut alice = Peer::config(
        "alice",
        Config {
-
            connect: vec![bob.addr(), eve.addr()],
+
            connect: vec![bob.address(), eve.address()],
            ..Config::default()
        },
        [7, 7, 7, 7],