Skip to main content

rns_cli/
rnsd.rs

1use std::fs;
2use std::path::Path;
3use std::sync::mpsc;
4
5use crate::args::Args;
6use rns_net::storage;
7use rns_net::{Callbacks, InterfaceId, RnsNode};
8
9const VERSION: &str = env!("FULL_VERSION");
10
11struct DaemonCallbacks;
12
13impl Callbacks for DaemonCallbacks {
14    fn on_announce(&mut self, announced: rns_net::AnnouncedIdentity) {
15        log::info!(
16            "Announce received for {} (hops: {})",
17            hex(&announced.dest_hash.0),
18            announced.hops,
19        );
20    }
21
22    fn on_path_updated(&mut self, dest_hash: rns_net::DestHash, hops: u8) {
23        log::debug!("Path updated for {} (hops: {})", hex(&dest_hash.0), hops);
24    }
25
26    fn on_local_delivery(
27        &mut self,
28        dest_hash: rns_net::DestHash,
29        _raw: Vec<u8>,
30        _hash: rns_net::PacketHash,
31    ) {
32        log::debug!("Local delivery for {}", hex(&dest_hash.0));
33    }
34
35    fn on_interface_up(&mut self, id: InterfaceId) {
36        log::info!("Interface {} up", id.0);
37    }
38
39    fn on_interface_down(&mut self, id: InterfaceId) {
40        log::info!("Interface {} down", id.0);
41    }
42}
43
44pub fn main_entry() {
45    main_entry_from(Args::parse());
46}
47
48pub fn main_entry_from(args: Args) {
49    if args.has("version") {
50        println!("rnsd {}", VERSION);
51        return;
52    }
53
54    if args.has("help") || args.has("h") {
55        print_usage();
56        return;
57    }
58
59    if args.has("exampleconfig") {
60        print!("{}", EXAMPLE_CONFIG);
61        return;
62    }
63
64    let service_mode = args.has("s");
65    let config_path = args.config_path().map(|s| s.to_string());
66
67    let log_level = match args.verbosity {
68        0 => log::LevelFilter::Info,
69        1 => log::LevelFilter::Debug,
70        _ => log::LevelFilter::Trace,
71    };
72    let log_level = if args.quiet > 0 {
73        match args.quiet {
74            1 => log::LevelFilter::Warn,
75            _ => log::LevelFilter::Error,
76        }
77    } else {
78        log_level
79    };
80
81    if service_mode {
82        let config_dir =
83            storage::resolve_config_dir(config_path.as_ref().map(|s| Path::new(s.as_str())));
84        let logfile_path = config_dir.join("logfile");
85        match fs::OpenOptions::new()
86            .create(true)
87            .append(true)
88            .open(&logfile_path)
89        {
90            Ok(file) => {
91                env_logger::Builder::new()
92                    .filter_level(log_level)
93                    .format_timestamp_secs()
94                    .target(env_logger::Target::Pipe(Box::new(file)))
95                    .init();
96            }
97            Err(e) => {
98                eprintln!("Could not open logfile {}: {}", logfile_path.display(), e);
99                std::process::exit(1);
100            }
101        }
102    } else {
103        env_logger::Builder::new()
104            .filter_level(log_level)
105            .format_timestamp_secs()
106            .init();
107    }
108
109    log::info!("Starting rnsd {}", VERSION);
110
111    let node = RnsNode::from_config(
112        config_path.as_ref().map(|s| Path::new(s.as_str())),
113        Box::new(DaemonCallbacks),
114    );
115
116    let node = match node {
117        Ok(n) => n,
118        Err(e) => {
119            log::error!("Failed to start: {}", e);
120            std::process::exit(1);
121        }
122    };
123
124    let (stop_tx, stop_rx) = mpsc::channel::<()>();
125
126    unsafe {
127        libc::signal(
128            libc::SIGINT,
129            signal_handler as *const () as libc::sighandler_t,
130        );
131        libc::signal(
132            libc::SIGTERM,
133            signal_handler as *const () as libc::sighandler_t,
134        );
135    }
136    lock_stop_tx().replace(stop_tx);
137
138    log::info!("rnsd started");
139
140    loop {
141        match stop_rx.recv_timeout(std::time::Duration::from_secs(1)) {
142            Ok(()) => break,
143            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => continue,
144            Err(_) => break,
145        }
146    }
147
148    log::info!("Shutting down...");
149    node.shutdown();
150    log::info!("rnsd stopped");
151}
152
153fn hex(bytes: &[u8]) -> String {
154    bytes.iter().map(|b| format!("{:02x}", b)).collect()
155}
156
157static STOP_TX: std::sync::Mutex<Option<mpsc::Sender<()>>> = std::sync::Mutex::new(None);
158
159fn lock_stop_tx() -> std::sync::MutexGuard<'static, Option<mpsc::Sender<()>>> {
160    match STOP_TX.lock() {
161        Ok(guard) => guard,
162        Err(poisoned) => {
163            log::warn!("recovering poisoned rnsd stop channel mutex");
164            poisoned.into_inner()
165        }
166    }
167}
168
169extern "C" fn signal_handler(_sig: libc::c_int) {
170    let guard = lock_stop_tx();
171    if let Some(ref tx) = *guard {
172        let _ = tx.send(());
173    }
174}
175
176fn print_usage() {
177    println!("Usage: rnsd [OPTIONS]");
178    println!();
179    println!("Options:");
180    println!("  --config PATH, -c PATH  Path to config directory");
181    println!("  -s                      Service mode (log to file)");
182    println!("  --exampleconfig         Print example config and exit");
183    println!("  -v                      Increase verbosity (can repeat)");
184    println!("  -q                      Decrease verbosity (can repeat)");
185    println!("  --version               Print version and exit");
186    println!("  --help, -h              Print this help");
187}
188
189const EXAMPLE_CONFIG: &str = r#"# This is an example Reticulum config file.
190# It can be used as a starting point for your own configuration.
191
192[reticulum]
193  enable_transport = false
194  share_instance = true
195  shared_instance_port = 37428
196  instance_control_port = 37429
197  panic_on_interface_error = false
198
199[logging]
200  loglevel = 4
201
202# ─── Interface examples ──────────────────────────────────────────────
203
204# TCP client: connect to a remote transport node
205#
206# [[TCP Client]]
207#   type = TCPClientInterface
208#   target_host = amsterdam.connect.reticulum.network
209#   target_port = 4965
210
211# TCP server: accept incoming connections
212#
213# [[TCP Server]]
214#   type = TCPServerInterface
215#   listen_ip = 0.0.0.0
216#   listen_port = 4965
217
218# UDP interface: broadcast on LAN
219#
220# [[UDP Interface]]
221#   type = UDPInterface
222#   listen_ip = 0.0.0.0
223#   listen_port = 4242
224#   forward_ip = 255.255.255.255
225#   forward_port = 4242
226
227# Serial interface: point-to-point serial port
228#
229# [[Serial Interface]]
230#   type = SerialInterface
231#   port = /dev/ttyUSB0
232#   speed = 115200
233#   databits = 8
234#   parity = none
235#   stopbits = 1
236
237# KISS interface: for TNC modems
238#
239# [[KISS Interface]]
240#   type = KISSInterface
241#   port = /dev/ttyUSB1
242#   speed = 115200
243#   databits = 8
244#   parity = none
245#   stopbits = 1
246#   preamble = 350
247#   txtail = 20
248#   persistence = 64
249#   slottime = 20
250#   flow_control = false
251
252# RNode LoRa interface
253#
254# [[RNode LoRa Interface]]
255#   type = RNodeInterface
256#   port = /dev/ttyACM0
257#   frequency = 867200000
258#   bandwidth = 125000
259#   txpower = 7
260#   spreadingfactor = 8
261#   codingrate = 5
262
263# Pipe interface: stdin/stdout of a subprocess
264#
265# [[Pipe Interface]]
266#   type = PipeInterface
267#   command = cat
268
269# Backbone interface: TCP mesh
270#
271# [[Backbone]]
272#   type = BackboneInterface
273#   listen_ip = 0.0.0.0
274#   listen_port = 4243
275#   peers = 10.0.0.1:4243, 10.0.0.2:4243
276"#;